@amatiscorp/disguard 1.1.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.
Files changed (70) hide show
  1. package/README.md +384 -22
  2. package/dist/AntiSpam.d.ts +15 -1
  3. package/dist/AntiSpam.d.ts.map +1 -1
  4. package/dist/AntiSpam.js +159 -12
  5. package/dist/AntiSpam.js.map +1 -1
  6. package/dist/defaults.d.ts.map +1 -1
  7. package/dist/defaults.js +79 -2
  8. package/dist/defaults.js.map +1 -1
  9. package/dist/detectors/accounts.d.ts +3 -0
  10. package/dist/detectors/accounts.d.ts.map +1 -0
  11. package/dist/detectors/accounts.js +50 -0
  12. package/dist/detectors/accounts.js.map +1 -0
  13. package/dist/detectors/duplicates.d.ts.map +1 -1
  14. package/dist/detectors/duplicates.js +5 -1
  15. package/dist/detectors/duplicates.js.map +1 -1
  16. package/dist/detectors/files.d.ts.map +1 -1
  17. package/dist/detectors/files.js +10 -0
  18. package/dist/detectors/files.js.map +1 -1
  19. package/dist/detectors/flood.d.ts.map +1 -1
  20. package/dist/detectors/flood.js +7 -1
  21. package/dist/detectors/flood.js.map +1 -1
  22. package/dist/detectors/ghost.d.ts +4 -0
  23. package/dist/detectors/ghost.d.ts.map +1 -0
  24. package/dist/detectors/ghost.js +34 -0
  25. package/dist/detectors/ghost.js.map +1 -0
  26. package/dist/detectors/hop.d.ts +3 -0
  27. package/dist/detectors/hop.d.ts.map +1 -0
  28. package/dist/detectors/hop.js +30 -0
  29. package/dist/detectors/hop.js.map +1 -0
  30. package/dist/detectors/images.d.ts +1 -0
  31. package/dist/detectors/images.d.ts.map +1 -1
  32. package/dist/detectors/images.js +19 -3
  33. package/dist/detectors/images.js.map +1 -1
  34. package/dist/detectors/length.d.ts +3 -0
  35. package/dist/detectors/length.d.ts.map +1 -0
  36. package/dist/detectors/length.js +23 -0
  37. package/dist/detectors/length.js.map +1 -0
  38. package/dist/detectors/links.d.ts.map +1 -1
  39. package/dist/detectors/links.js +15 -1
  40. package/dist/detectors/links.js.map +1 -1
  41. package/dist/detectors/mentions.d.ts.map +1 -1
  42. package/dist/detectors/mentions.js +19 -0
  43. package/dist/detectors/mentions.js.map +1 -1
  44. package/dist/detectors/punctuation.d.ts +3 -0
  45. package/dist/detectors/punctuation.d.ts.map +1 -0
  46. package/dist/detectors/punctuation.js +26 -0
  47. package/dist/detectors/punctuation.js.map +1 -0
  48. package/dist/detectors/spoilers.d.ts +3 -0
  49. package/dist/detectors/spoilers.d.ts.map +1 -0
  50. package/dist/detectors/spoilers.js +25 -0
  51. package/dist/detectors/spoilers.js.map +1 -0
  52. package/dist/detectors/words.d.ts +3 -0
  53. package/dist/detectors/words.d.ts.map +1 -0
  54. package/dist/detectors/words.js +56 -0
  55. package/dist/detectors/words.js.map +1 -0
  56. package/dist/enforcement.d.ts +1 -0
  57. package/dist/enforcement.d.ts.map +1 -1
  58. package/dist/enforcement.js +57 -9
  59. package/dist/enforcement.js.map +1 -1
  60. package/dist/index.d.ts +4 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +9 -1
  63. package/dist/index.js.map +1 -1
  64. package/dist/locale.d.ts +14 -0
  65. package/dist/locale.d.ts.map +1 -0
  66. package/dist/locale.js +35 -0
  67. package/dist/locale.js.map +1 -0
  68. package/dist/types.d.ts +103 -15
  69. package/dist/types.d.ts.map +1 -1
  70. 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
+ [![npm version](https://img.shields.io/npm/v/@amatiscorp/disguard.svg?style=flat-square)](https://www.npmjs.com/package/@amatiscorp/disguard)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=flat-square)](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, repeated text, phishing / unwanted links, duplicate images, mention spam, excessive caps, and emoji spam.
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,100 @@ 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
+
92
+ ## What's new in 1.2.0
93
+
94
+ More knobs, per-channel rules, and bilingual user-facing text.
95
+
96
+ | Option | What it does |
97
+ | --- | --- |
98
+ | `locale` | `"en"` or `"es"` for warn + log labels. Empty `warnMessage` uses the locale template. |
99
+ | `ignorePermissions` | Skip members with those Discord permission names (`ManageMessages`, …). |
100
+ | `setChannelConfig(guildId, channelId, patch)` | Rules for one channel (on top of guild + global). |
101
+ | `channelOverrides` | Same thing in static config: `{ "CHANNEL_ID": { flood: { maxMessages: 10 } } }`. |
102
+ | `disabledDetectors` | Turn off detectors by name without touching each block. |
103
+ | `detectorOrder` | Custom detector run order. |
104
+ | `flood.sameChannelOnly` / `duplicates.sameChannelOnly` | Count only the current channel. |
105
+ | `links.maxLinks` / `links.scanEmbeds` | Cap URLs per message; toggle embed scanning. |
106
+ | `images.skipContentTypes` / `images.maxAttachments` | Ignore GIFs, limit images per message. |
107
+ | `mentions.maxRepeatsOfSame` | Same user pinged over and over in one message. |
108
+ | `accounts` | Flag brand-new accounts (`minAgeDays`) or default avatars. Off by default. |
109
+ | `length` | Flag walls of text. Off by default. |
110
+ | `punishment.timeout.scale` | `"none"` \| `"linear"` \| `"exponential"` with `maxDurationMs`. |
111
+ | `punishment.warnAsEmbed` | Warning as an embed. |
112
+
113
+ ```js
114
+ const antispam = new AntiSpam(client, {
115
+ preset: "balanced",
116
+ locale: "es",
117
+ ignorePermissions: ["ManageMessages", "ModerateMembers"],
118
+ flood: { sameChannelOnly: true },
119
+ links: { maxLinks: 3, scanEmbeds: true },
120
+ accounts: { enabled: true, minAgeDays: 2 },
121
+ punishment: {
122
+ warnAsEmbed: true,
123
+ timeout: { enabled: true, durationMs: 60_000, scale: "linear", maxDurationMs: 10 * 60_000 },
124
+ },
125
+ disabledDetectors: ["caps"],
126
+ channelOverrides: {
127
+ "MEMES_CHANNEL_ID": { images: { maxRepeats: 8 }, emojis: { maxEmojis: 30 } },
128
+ },
129
+ });
130
+
131
+ antispam.setChannelConfig(guildId, channelId, { flood: { maxMessages: 12 } });
132
+ ```
133
+
24
134
  ## What's new in 1.1.0
25
135
 
26
136
  - **Punishment cooldown** — after a strike, Disguard waits `punishment.cooldownMs` (8s by default) before warning / timeout / kick / ban again. Extra spam in that window is deleted quietly. This stops the “4 timeouts in one flood” you saw in 1.0.0.
@@ -63,6 +173,8 @@ console.log(antispam.getStats());
63
173
 
64
174
  ## Table of contents
65
175
 
176
+ - [What's new in 1.3.0](#whats-new-in-130)
177
+ - [What's new in 1.2.0](#whats-new-in-120)
66
178
  - [What's new in 1.1.0](#whats-new-in-110)
67
179
  - [Features](#features)
68
180
  - [Requirements](#requirements)
@@ -95,15 +207,23 @@ console.log(antispam.getStats());
95
207
  | **file** | Dangerous attachments (`.exe`, `.bat`, `.dll`, `.msi`, …) |
96
208
  | **zalgo** | Obfuscated / zalgo text (stacked combining marks) |
97
209
  | **newline** | Message walls made of empty lines |
210
+ | **account** | Brand-new Discord accounts / default avatar / new guild members (off by default) |
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) |
98
217
 
99
218
  Also included:
100
219
 
101
220
  - Three presets: `lenient`, `balanced`, `strict`
102
- - Ignore lists for users, roles, channels, categories, and guilds
221
+ - Ignore lists for users, roles, channels, categories, guilds, and command prefixes
222
+ - Per-guild, per-channel, and per-role overlays
103
223
  - Strike system with decay
104
- - 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)
105
225
  - `dryRun` to tune rules without punishing anyone
106
- - Edit scanning (links and mentions)
226
+ - Edit scanning (links, mentions, and words)
107
227
  - Zero extra runtime dependencies (peer: `discord.js`)
108
228
  - In-memory store only — no database required
109
229
  - Full TypeScript types
@@ -218,6 +338,7 @@ npm run dev
218
338
  | Warn in the channel | **Send Messages** |
219
339
  | Log embeds | **Embed Links** |
220
340
  | Kick / ban (opt-in) | **Kick Members** / **Ban Members** |
341
+ | Mute / add / remove roles (opt-in) | **Manage Roles** |
221
342
 
222
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`.
223
344
 
@@ -225,11 +346,12 @@ Enable **Message Content Intent** in the [Discord Developer Portal](https://disc
225
346
 
226
347
  ## How it works
227
348
 
228
- 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.
229
350
  2. Keeps a short **in-memory** history per user and guild (no database).
230
- 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.
231
352
  4. Adds one strike (with optional decay) and applies the configured punishment.
232
- 5. Message edits only re-check **links** and **mentions**, so editing `hello` into a phishing URL is still caught without counting as flood.
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.
233
355
 
234
356
  Call `antispam.stop()` on shutdown, hot reload, or plugin unload.
235
357
 
@@ -259,6 +381,8 @@ new AntiSpam(client, {
259
381
  | Mentions | 10 | 6 | 3 |
260
382
  | Block invites | no | no | yes |
261
383
  | Block shorteners | no | yes | yes |
384
+ | Channel hop | off | 5 / 8s | 3 / 6s |
385
+ | Ghost pings | off | off | on |
262
386
  | Auto timeout | off | 60s from 2 strikes | 5 min from 1 strike |
263
387
 
264
388
  ## Full configuration
@@ -275,8 +399,17 @@ Every option is optional. Missing fields fall back to the preset (or `balanced`
275
399
  | `ignoreWebhooks` | `boolean` | `true` | Skip webhooks. |
276
400
  | `ignoreOwner` | `boolean` | `true` | Skip the guild owner. |
277
401
  | `ignoreAdministrators` | `boolean` | `true` | Skip members with Administrator. |
278
- | `checkEdits` | `boolean` | `true` | Re-scan edits for links and mentions. |
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. |
279
406
  | `cleanupIntervalMs` | `number` | `60000` | How often the memory store is pruned. |
407
+ | `locale` | `"en"` \| `"es"` | `"en"` | Language for warnings and log embeds. |
408
+ | `ignorePermissions` | `string[]` | `[]` | Permission names that bypass checks. |
409
+ | `disabledDetectors` | `DetectorType[]` | `[]` | Detectors to skip. |
410
+ | `detectorOrder` | `DetectorType[]` | `[]` | Custom order. Empty = default. |
411
+ | `channelOverrides` | `object` | `{}` | Per-channel patches keyed by channel id. |
412
+ | `roleOverrides` | `object` | `{}` | Per-role patches keyed by role id. |
280
413
 
281
414
  ### Ignore lists
282
415
 
@@ -287,10 +420,11 @@ ignored: {
287
420
  channels: ["789"],
288
421
  categories: ["101"],
289
422
  guilds: ["202"],
423
+ prefixes: ["!", "/", "."],
290
424
  }
291
425
  ```
292
426
 
293
- 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).
294
428
 
295
429
  ### Flood
296
430
 
@@ -337,6 +471,9 @@ links: {
337
471
  suspiciousTlds: [], // e.g. ["tk", "gq"]
338
472
  customPatterns: [], // regex against the whole message
339
473
  extraPhishingKeywords: ["fake giveaway"],
474
+ maxLinks: 0,
475
+ scanEmbeds: true,
476
+ blockOauth: true,
340
477
  severity: "high",
341
478
  }
342
479
  ```
@@ -352,6 +489,7 @@ Built-in checks (each can be turned off):
352
489
  - Scam phrases plus any link (`free nitro`, `steam gift`, `verifica tu cuenta`, …)
353
490
  - Discord invite links if `blockInvites` is `true`
354
491
  - Markdown-masked links: `[click](https://evil.test)`
492
+ - Login / OAuth-looking paths on non-allowlisted hosts if `blockOauth` is `true`
355
493
 
356
494
  ```js
357
495
  // Block foreign server invites
@@ -417,12 +555,31 @@ emojis: {
417
555
  }
418
556
  ```
419
557
 
558
+ ### Accounts and length
559
+
560
+ ```js
561
+ accounts: {
562
+ enabled: false, // turn on to flag new users
563
+ minAgeDays: 3,
564
+ blockDefaultAvatar: false,
565
+ minGuildAgeDays: 0, // 0 = off; e.g. 1 = joined this guild today
566
+ severity: "medium",
567
+ }
568
+
569
+ length: {
570
+ enabled: false,
571
+ maxCharacters: 1000,
572
+ severity: "low",
573
+ }
574
+ ```
575
+
420
576
  ### Files, zalgo, newlines
421
577
 
422
578
  ```js
423
579
  files: {
424
580
  enabled: true,
425
581
  blockedExtensions: ["exe", "bat", "cmd", "com", "scr", "dll", "msi", "vbs", "ps1", "jar", "apk"],
582
+ maxBytes: 0, // 0 = unlimited
426
583
  severity: "critical",
427
584
  }
428
585
 
@@ -439,6 +596,57 @@ newlines: {
439
596
  }
440
597
  ```
441
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
+
442
650
  ### Punishment
443
651
 
444
652
  Kick and ban stay **disabled** on purpose. Turn them on only if you really want that.
@@ -457,12 +665,20 @@ punishment: {
457
665
  strikeDecayMs: 15 * 60_000, // strikes expire after 15 minutes
458
666
  cooldownMs: 8_000, // no second timeout/warn during this window
459
667
  deleteDuringCooldown: true, // still delete extra spam quietly
668
+ warnAsEmbed: false,
669
+ addRoleIds: [], // e.g. ["MUTE_ROLE_ID"] — needs Manage Roles
670
+ removeRoleIds: [],
671
+ minStrikesForRoles: 1,
460
672
  }
673
+
674
+ // timeout.scale: "none" | "linear" (base * strikes) | "exponential" (base * 2^n)
675
+ // timeout.maxDurationMs caps the result (Discord max = 28 days)
676
+
461
677
  ```
462
678
 
463
679
  `warnMessage` placeholders: `{user}` `{reason}` `{type}` `{strikes}`.
464
680
 
465
- 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`.
466
682
 
467
683
  If the bot lacks a permission, or the target is higher in the hierarchy, that action is skipped and listed in `result.skipped`.
468
684
 
@@ -480,6 +696,9 @@ new AntiSpam(client, {
480
696
  onError(error, context) {
481
697
  console.error("[disguard]", context, error);
482
698
  },
699
+ onCooldown(message) {
700
+ // Extra spam during punishment.cooldownMs (already deleted if deleteDuringCooldown).
701
+ },
483
702
  });
484
703
  ```
485
704
 
@@ -487,7 +706,9 @@ new AntiSpam(client, {
487
706
 
488
707
  ```ts
489
708
  {
490
- 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",
491
712
  severity: "low" | "medium" | "high" | "critical",
492
713
  userId: string,
493
714
  guildId: string,
@@ -495,7 +716,7 @@ new AntiSpam(client, {
495
716
  messageId: string,
496
717
  reason: string,
497
718
  details: Record<string, unknown>,
498
- recommendedActions: Array<"delete" | "warn" | "timeout" | "kick" | "ban">,
719
+ recommendedActions: Array<"delete" | "warn" | "timeout" | "kick" | "ban" | "addRole" | "removeRole">,
499
720
  timestamp: number,
500
721
  }
501
722
  ```
@@ -522,6 +743,9 @@ const antispam = new AntiSpam(client, options);
522
743
 
523
744
  antispam.start();
524
745
  antispam.stop();
746
+ antispam.pause();
747
+ antispam.resume();
748
+ antispam.isPaused();
525
749
 
526
750
  const config = antispam.getConfig();
527
751
  antispam.setConfig({ flood: { maxMessages: 8 } }); // deep merge, other keys stay
@@ -533,6 +757,11 @@ antispam.isCoolingDown(guildId, userId);
533
757
  antispam.setGuildConfig(guildId, { flood: { maxMessages: 3 } });
534
758
  antispam.getGuildConfig(guildId);
535
759
  antispam.clearGuildConfig(guildId);
760
+ antispam.setChannelConfig(guildId, channelId, { images: { maxRepeats: 8 } });
761
+ antispam.getChannelConfig(guildId, channelId);
762
+ antispam.clearChannelConfig(guildId, channelId);
763
+ antispam.setRoleConfig(roleId, { flood: { maxMessages: 12 } });
764
+ antispam.clearRoleConfig(roleId);
536
765
 
537
766
  antispam.use(customDetector);
538
767
  antispam.getStats();
@@ -617,6 +846,18 @@ antispam.setConfig(resolveConfig("strict", { ignored: antispam.getConfig().ignor
617
846
  images: { hashMode: "content", maxDownloadBytes: 1_000_000 }
618
847
  ```
619
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
+
620
861
  **Reset a user after a false positive**
621
862
 
622
863
  ```js
@@ -671,9 +912,14 @@ Wait a few seconds between categories so flood does not eat the next test.
671
912
  | File | Upload a dummy `.exe` or `.bat` |
672
913
  | Zalgo | Text with many stacked combining marks |
673
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`) |
674
920
  | Edit | Send `hello`, then edit it to `Free Nitro https://bit.ly/test` |
675
921
 
676
- 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`.
677
923
 
678
924
  The second strike applies a 1 minute timeout with the default example config. Strikes decay after 15 minutes, or call `resetUser`.
679
925
 
@@ -714,6 +960,83 @@ MIT
714
960
 
715
961
  # Español
716
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
+
1002
+ ## Novedades de 1.2.0
1003
+
1004
+ Más opciones, reglas por canal y textos al usuario en inglés o español.
1005
+
1006
+ | Opción | Qué hace |
1007
+ | --- | --- |
1008
+ | `locale` | `"en"` o `"es"` para avisos y logs. Si `warnMessage` está vacío, usa la plantilla del idioma. |
1009
+ | `ignorePermissions` | Ignora quien tenga esos permisos (`ManageMessages`, …). |
1010
+ | `setChannelConfig` | Config de un canal (encima de guild + global). |
1011
+ | `channelOverrides` | Lo mismo en la config estática. |
1012
+ | `disabledDetectors` | Apaga detectores por nombre. |
1013
+ | `detectorOrder` | Orden de ejecución. |
1014
+ | `flood.sameChannelOnly` / `duplicates.sameChannelOnly` | Solo cuenta el canal actual. |
1015
+ | `links.maxLinks` / `links.scanEmbeds` | Tope de URLs; escanear embeds. |
1016
+ | `images.skipContentTypes` / `images.maxAttachments` | Ignorar gifs, limitar adjuntos. |
1017
+ | `mentions.maxRepeatsOfSame` | El mismo ping repetido. |
1018
+ | `accounts` | Cuentas nuevas (`minAgeDays`) o avatar por defecto. Apagado por defecto. |
1019
+ | `length` | Mensajes kilométricos. Apagado por defecto. |
1020
+ | `punishment.timeout.scale` | `"none"` \| `"linear"` \| `"exponential"`. |
1021
+ | `punishment.warnAsEmbed` | Aviso en embed. |
1022
+
1023
+ ```js
1024
+ new AntiSpam(client, {
1025
+ locale: "es",
1026
+ ignorePermissions: ["ManageMessages"],
1027
+ flood: { sameChannelOnly: true },
1028
+ links: { maxLinks: 3 },
1029
+ accounts: { enabled: true, minAgeDays: 2 },
1030
+ punishment: {
1031
+ warnAsEmbed: true,
1032
+ timeout: { durationMs: 60_000, scale: "linear", maxDurationMs: 600_000 },
1033
+ },
1034
+ channelOverrides: {
1035
+ "ID_CANAL_MEMES": { emojis: { maxEmojis: 30 } },
1036
+ },
1037
+ });
1038
+ ```
1039
+
717
1040
  ## Novedades de 1.1.0
718
1041
 
719
1042
  - **Cooldown de castigo** — tras un strike espera `punishment.cooldownMs` (8s por defecto) antes de volver a avisar o timeout. El spam extra de esa ventana se borra en silencio. Así no salen 4 timeouts en el mismo flood.
@@ -726,6 +1049,8 @@ MIT
726
1049
 
727
1050
  ## Tabla de contenidos
728
1051
 
1052
+ - [Novedades de 1.3.0](#novedades-de-130)
1053
+ - [Novedades de 1.2.0](#novedades-de-120)
729
1054
  - [Novedades de 1.1.0](#novedades-de-110)
730
1055
  - [Qué es](#qué-es)
731
1056
  - [Requisitos](#requisitos)
@@ -748,7 +1073,7 @@ MIT
748
1073
 
749
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.
750
1075
 
751
- Detecta flood, texto repetido, phishing / enlaces no deseados (también en embeds), imágenes repetidas, spam de menciones, mayúsculas, emojis, archivos peligrosos, zalgo y paredes de líneas.
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.
752
1077
 
753
1078
  ## Requisitos
754
1079
 
@@ -834,6 +1159,7 @@ npm run dev
834
1159
  | Avisar en el canal | **Send Messages** |
835
1160
  | Embed de logs | **Embed Links** |
836
1161
  | Kick / ban (opt-in) | **Kick Members** / **Ban Members** |
1162
+ | Mute / add / remove roles (opt-in) | **Manage Roles** |
837
1163
 
838
1164
  No puede sancionar al dueño del servidor ni a quien esté por encima del bot en la jerarquía.
839
1165
 
@@ -841,11 +1167,12 @@ Activa **Message Content Intent** en el [Portal de Discord](https://discord.com/
841
1167
 
842
1168
  ## Cómo funciona
843
1169
 
844
- 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.
845
1171
  2. Guarda en **memoria** un historial corto por usuario y servidor. Sin base de datos.
846
- 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.
847
1173
  4. Suma un strike (con caducidad) y aplica el castigo configurado.
848
- 5. Las ediciones solo revisan **enlaces** y **menciones**.
1174
+ 5. Las ediciones revisan **enlaces**, **menciones** y **palabras**.
1175
+ 6. Los borrados pueden disparar **ghost** ping si `ghostPing.enabled` es true.
849
1176
 
850
1177
  Llama `antispam.stop()` al apagar el proceso.
851
1178
 
@@ -878,8 +1205,12 @@ Todo es opcional. Lo que no pongas usa el preset.
878
1205
  | `ignoreWebhooks` | `boolean` | `true` | Ignora webhooks. |
879
1206
  | `ignoreOwner` | `boolean` | `true` | Ignora al dueño. |
880
1207
  | `ignoreAdministrators` | `boolean` | `true` | Ignora quien tenga Administrator. |
881
- | `checkEdits` | `boolean` | `true` | Revisa ediciones (enlaces y menciones). |
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. |
882
1212
  | `cleanupIntervalMs` | `number` | `60000` | Limpieza de memoria. |
1213
+ | `roleOverrides` | `object` | `{}` | Parches por rol. |
883
1214
 
884
1215
  ### Listas de ignore
885
1216
 
@@ -890,6 +1221,7 @@ ignored: {
890
1221
  channels: ["id"],
891
1222
  categories: ["id"],
892
1223
  guilds: ["id"],
1224
+ prefixes: ["!", "/", "."],
893
1225
  }
894
1226
  ```
895
1227
 
@@ -934,13 +1266,16 @@ links: {
934
1266
  suspiciousTlds: [],
935
1267
  customPatterns: ["steamcommunity\\.ru"],
936
1268
  extraPhishingKeywords: ["sorteo falso"],
1269
+ maxLinks: 0,
1270
+ scanEmbeds: true,
1271
+ blockOauth: true,
937
1272
  severity: "high",
938
1273
  }
939
1274
  ```
940
1275
 
941
1276
  La `allowList` gana a las heurísticas. `discord.com` y `youtube.com` ya vienen permitidos.
942
1277
 
943
- Heurísticas incluidas (todas se pueden apagar): acortadores, IPs, punycode, clones de marcas, palabras de estafa + enlace, invitaciones si `blockInvites` es `true`, y enlaces enmascarados `[texto](url)`.
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`.
944
1279
 
945
1280
  ### Imágenes
946
1281
 
@@ -972,11 +1307,23 @@ emojis: { enabled: true, maxEmojis: 12, maxStickers: 3, severity: "low" },
972
1307
  ### Archivos, zalgo, saltos de línea
973
1308
 
974
1309
  ```js
975
- 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" },
976
1311
  zalgo: { enabled: true, maxCombining: 20, severity: "medium" },
977
1312
  newlines: { enabled: true, maxNewlines: 15, severity: "low" },
978
1313
  ```
979
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
+
980
1327
  ### Castigos
981
1328
 
982
1329
  Kick y ban van **apagados**. Actívalos solo si lo tienes claro.
@@ -995,12 +1342,15 @@ punishment: {
995
1342
  strikeDecayMs: 15 * 60_000,
996
1343
  cooldownMs: 8_000,
997
1344
  deleteDuringCooldown: true,
1345
+ addRoleIds: [],
1346
+ removeRoleIds: [],
1347
+ minStrikesForRoles: 1,
998
1348
  }
999
1349
  ```
1000
1350
 
1001
1351
  Placeholders: `{user}` `{reason}` `{type}` `{strikes}`.
1002
1352
 
1003
- 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.
1004
1354
 
1005
1355
  ## Callbacks
1006
1356
 
@@ -1012,6 +1362,7 @@ new AntiSpam(client, {
1012
1362
  onError(error, context) {
1013
1363
  console.error(context, error);
1014
1364
  },
1365
+ onCooldown(message) {},
1015
1366
  });
1016
1367
  ```
1017
1368
 
@@ -1024,11 +1375,15 @@ const antispam = new AntiSpam(client, options);
1024
1375
 
1025
1376
  antispam.start();
1026
1377
  antispam.stop();
1378
+ antispam.pause();
1379
+ antispam.resume();
1027
1380
  antispam.getConfig();
1028
1381
  antispam.setConfig({ flood: { maxMessages: 8 } });
1029
1382
  antispam.getStrikes(guildId, userId);
1030
1383
  antispam.resetUser(guildId, userId);
1031
1384
  antispam.setGuildConfig(guildId, { flood: { maxMessages: 3 } });
1385
+ antispam.setChannelConfig(guildId, channelId, { flood: { maxMessages: 12 } });
1386
+ antispam.setRoleConfig(roleId, { emojis: { maxEmojis: 40 } });
1032
1387
  antispam.getStats();
1033
1388
 
1034
1389
  const incident = await antispam.analyze(message);
@@ -1060,6 +1415,8 @@ new AntiSpam(client, {
1060
1415
 
1061
1416
  **Quitar strikes:** `antispam.resetUser(guildId, userId)`.
1062
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
+
1063
1420
  **Cambiar a strict en caliente**
1064
1421
 
1065
1422
  ```js
@@ -1097,9 +1454,14 @@ Espera unos segundos entre categorías.
1097
1454
  | Archivo | Sube un `.exe` o `.bat` (aunque sea de mentira) |
1098
1455
  | Zalgo | Texto con muchos diacríticos apilados |
1099
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`) |
1100
1462
  | Edición | Manda `hola` y edítalo a `Free Nitro https://bit.ly/test` |
1101
1463
 
1102
- 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`.
1103
1465
 
1104
1466
  Al segundo strike el ejemplo mete timeout de 1 minuto. Los strikes caducan a los 15 minutos, o usa `resetUser`.
1105
1467