iraqi-dialect-bad-words 1.2.1 → 1.4.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 +132 -14
- package/dist/index.d.mts +171 -22
- package/dist/index.d.ts +171 -22
- package/dist/index.js +535 -56
- package/dist/index.mjs +535 -56
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
A profanity filter for the Iraqi Arabic dialect. Detects and masks bad words in user-generated text.
|
|
8
8
|
|
|
9
|
-
Works in **React Native / Expo**, **React** (Vite, Next.js, CRA, …) and **Node.js**, with TypeScript types included. No dependencies.
|
|
9
|
+
Works in **React Native / Expo**, **React** (Vite, Next.js, CRA, …), **Vue** (Vite, Nuxt) and **Node.js**, with TypeScript types included. No dependencies.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -55,6 +55,37 @@ export function CommentInput() {
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
### Vue example
|
|
59
|
+
|
|
60
|
+
Create the filter once in its own module and import it where you need it. Keep it out of `ref()` and `reactive()`: the filter never changes, and wrapping it in reactive proxies only slows every check down.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// src/profanity.ts
|
|
64
|
+
import IraqiBadWordsFilter from 'iraqi-dialect-bad-words';
|
|
65
|
+
|
|
66
|
+
export const filter = new IraqiBadWordsFilter();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
import { ref, computed } from 'vue';
|
|
72
|
+
import { filter } from '@/profanity';
|
|
73
|
+
|
|
74
|
+
const text = ref('');
|
|
75
|
+
const isProfane = computed(() => filter.isProfane(text.value));
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<template>
|
|
79
|
+
<textarea v-model="text" dir="auto" />
|
|
80
|
+
<p v-if="isProfane">Please keep it respectful.</p>
|
|
81
|
+
<button :disabled="isProfane">Send</button>
|
|
82
|
+
</template>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
To highlight bad words, render the text in pieces split at the `start` and `end` of each match from [`inspect()`](#inspecttext-string-options-profanitymatch) rather than with `v-html`, so user text can't inject HTML.
|
|
86
|
+
|
|
87
|
+
In any framework, check the text on your server too (a Nuxt server route or any Node backend can use the same package): a check in the browser can be skipped by calling your API directly.
|
|
88
|
+
|
|
58
89
|
## API
|
|
59
90
|
|
|
60
91
|
### `new IraqiBadWordsFilter(options?)`
|
|
@@ -64,16 +95,48 @@ Creates a filter with the built-in word list.
|
|
|
64
95
|
```ts
|
|
65
96
|
new IraqiBadWordsFilter(); // profanity only
|
|
66
97
|
new IraqiBadWordsFilter({ includeInsults: true }); // also milder insults
|
|
98
|
+
new IraqiBadWordsFilter({ includePhrases: true }); // also vulgar phrases
|
|
67
99
|
new IraqiBadWordsFilter({ stripSuffixes: true }); // also unlisted suffix forms
|
|
100
|
+
new IraqiBadWordsFilter({ dialects: ['mosul'] }); // also Mosul ر → غ forms
|
|
68
101
|
new IraqiBadWordsFilter({ customWords: ['word1', 'word2'] });
|
|
102
|
+
new IraqiBadWordsFilter({ safeWords: ['word3'] }); // clean words not to flag
|
|
69
103
|
new IraqiBadWordsFilter(['word1', 'word2']); // same as customWords
|
|
70
104
|
```
|
|
71
105
|
|
|
72
106
|
| Option | Default | Description |
|
|
73
107
|
|---|---|---|
|
|
74
|
-
| `customWords` | `[]` | Extra words to filter. |
|
|
108
|
+
| `customWords` | `[]` | Extra words to filter, on top of the built-in list. Can come from your own JSON file (see below). |
|
|
109
|
+
| `safeWords` | `[]` | Extra safe stems, on top of the built-in [`safe-stems.json`](src/safe-stems.json): clean words that would otherwise match once a prefix or suffix is stripped, like the built-in `بعير` ("camel" = `ب` + `عير`). Use it for clean words your app sees flagged, especially ones your own `customWords` cause. Their prefixed and suffixed forms are safe too (`وبعيرك`). Can come from your own JSON file (see below). |
|
|
75
110
|
| `includeInsults` | `false` | Also filter milder insults like كلب, حمار, قندرة, نعال. Off by default because many of them are everyday words, so turning it on flags sentences like `عندي كلب` ("I have a dog"). |
|
|
111
|
+
| `includePhrases` | `false` | Also filter the phrases in [`phrases.json`](src/phrases.json): clean words that are vulgar together, like `تعال العب بيه`. Off by default because the same words can be meant literally ("come play with it"). |
|
|
112
|
+
| `customPhrases` | `[]` | Extra phrases to filter, whether or not `includePhrases` is on. Can come from your own JSON file. |
|
|
76
113
|
| `stripSuffixes` | `false` | Also match words with a pronoun suffix (ي ك ج ه ها نا كم هم) that the list doesn't spell out, e.g. `طيزهم` → `طيز`. Off by default because it widens matching: a clean word that ends like a bad word plus a suffix gets flagged too. |
|
|
114
|
+
| `dialects` | `[]` | Regional pronunciations to also match. `'mosul'`: ر said as غ, so every entry with ر also matches its غ form (`شرموطة` → `شغموطة`); the ر forms keep matching. Safe stems get their غ form too. |
|
|
115
|
+
|
|
116
|
+
#### Using your own word files
|
|
117
|
+
|
|
118
|
+
Keep your app's extra words, and clean words you don't want flagged, in JSON files: arrays of single words, the same format as [`src/words.json`](src/words.json) and [`src/safe-stems.json`](src/safe-stems.json).
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
["زفت", "نذل"]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Import them and pass them as `customWords` and `safeWords`. The built-in lists stay on, and your words are added to them:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import IraqiBadWordsFilter from 'iraqi-dialect-bad-words';
|
|
128
|
+
import myWords from './my-words.json'; // TypeScript needs "resolveJsonModule": true
|
|
129
|
+
import mySafeWords from './my-safe-words.json';
|
|
130
|
+
// CommonJS: const myWords = require('./my-words.json');
|
|
131
|
+
|
|
132
|
+
const filter = new IraqiBadWordsFilter({ customWords: myWords, safeWords: mySafeWords });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Your words go through the same normalization as the built-in lists, so you don't need to list diacritic, ة/ه, stretched or prefixed spellings. Whitespace around a word is trimmed and blank entries are skipped. `inspect()` reports a match from your word file with `source: 'custom'`, and a match cleared by your safe words with `suppressedBy` set to that safe word.
|
|
136
|
+
|
|
137
|
+
A safe word only clears a match that needed a prefix or suffix stripped, like the built-in safe stems. To stop a listed word itself from being flagged, use `removeWords()`.
|
|
138
|
+
|
|
139
|
+
React Native, Expo, Vite and Next.js bundle the JSON files into your app, so this works the same in each. The filter doesn't read files at runtime. If your lists come from a server, fetch them and pass the arrays to `addWords()` and `addSafeWords()`.
|
|
77
140
|
|
|
78
141
|
### `isProfane(text: string): boolean`
|
|
79
142
|
|
|
@@ -89,38 +152,80 @@ Lists each bad word in the text and explains why it matched. Use it to highlight
|
|
|
89
152
|
|
|
90
153
|
```ts
|
|
91
154
|
filter.inspect('روح للكواد!');
|
|
92
|
-
// [{ token: 'للكواد', start: 4, end: 10, normalized: 'لكواد',
|
|
93
|
-
//
|
|
155
|
+
// [{ token: 'للكواد', start: 4, end: 10, normalized: 'لكواد', entry: 'كواد',
|
|
156
|
+
// source: 'words', prefix: 'ل', suffix: '', rules: ['stretched', 'prefix'] }]
|
|
94
157
|
```
|
|
95
158
|
|
|
96
159
|
| Field | Description |
|
|
97
160
|
|---|---|
|
|
98
161
|
| `token` | The word exactly as written. |
|
|
99
162
|
| `start`, `end` | Offsets in the **original** text, so `text.slice(start, end) === token`. Counted in JavaScript string units (UTF-16); convert them if another platform counts characters differently. |
|
|
100
|
-
| `normalized` | The word after normalization (Arabizi is folded too, so `Qa7ba` shows as `gahba`). |
|
|
101
|
-
| `entry` | The list entry it matched, spelled as in the list. |
|
|
102
|
-
| `source` | `'words'`, `'insults'` or `'custom'`. A word on more than one list keeps the first: built-in, then insults, then custom. |
|
|
163
|
+
| `normalized` | The word after normalization (Arabizi is folded too, so `Qa7ba` shows as `gahba`). For a word with گ, the reading that matched (ق or ك). |
|
|
164
|
+
| `entry` | The list entry it matched, spelled as in the list. For a Mosul form, the listed ر spelling. |
|
|
165
|
+
| `source` | `'words'`, `'insults'`, `'phrases'` or `'custom'`. A word on more than one list keeps the first: built-in, then insults, then custom. |
|
|
103
166
|
| `prefix` | The attached prefix stripped before matching (after normalization, so `لل` shows as `ل`), or `''` for a direct match. |
|
|
104
167
|
| `suffix` | The suffix stripped before matching (only with `stripSuffixes`), or `''`. |
|
|
168
|
+
| `rules` | The rules the word needed to match the entry (see below). Empty when it's spelled exactly like the entry. |
|
|
105
169
|
| `suppressedBy` | Only with `includeSuppressed`: the safe stem that cleared this match. |
|
|
106
170
|
|
|
107
171
|
Pass `{ includeSuppressed: true }` to also see words that matched but were cleared by a safe stem. They are not profane (`isProfane` and `clean` ignore them), but they show why a word *wasn't* flagged:
|
|
108
172
|
|
|
109
173
|
```ts
|
|
110
174
|
filter.inspect('وبعيرك', { includeSuppressed: true });
|
|
111
|
-
// [{ token: 'وبعيرك', …, entry: 'عيرك', prefix: 'وب', suppressedBy: 'بعير' }]
|
|
175
|
+
// [{ token: 'وبعيرك', …, entry: 'عيرك', prefix: 'وب', rules: ['prefix'], suppressedBy: 'بعير' }]
|
|
112
176
|
```
|
|
113
177
|
|
|
114
|
-
|
|
178
|
+
`rules` lists only the rules that made a difference between the typed word and the entry, so a report shows straight away why something matched:
|
|
179
|
+
|
|
180
|
+
| Rule | Example |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `unicode-forms` | `ﻋﻴﺮ`, `gahba`, `𝐠𝐚𝐡𝐛𝐚` (presentation forms, full-width and mathematical letters) |
|
|
183
|
+
| `diacritics` | `عَارَة`, `عـــارة` |
|
|
184
|
+
| `invisible` | `عارة` (zero-width character inside) |
|
|
185
|
+
| `spelling` | `عاره` (ة/ه, ى/ي, أ/ا) |
|
|
186
|
+
| `keyboard` | `عیر`, `کس` (Persian/Kurdish letters) |
|
|
187
|
+
| `lookalike` | `gаhba` (Cyrillic `а` in a Latin word) |
|
|
188
|
+
| `arabic-digits` | `ga٧ba` |
|
|
189
|
+
| `stretched` | `عاااارة`, `للكواد` |
|
|
190
|
+
| `glued-digits` | `عارة3` |
|
|
191
|
+
| `arabizi` | `qa7ba` → `gahba` |
|
|
192
|
+
| `gaf-as-qaf` / `gaf-as-kaf` | `گحبة` → قحبة, `گسها` → كسها |
|
|
193
|
+
| `prefix` / `suffix` | `بطيزك`, `ياعارة`, `طيزهم` (with `stripSuffixes`) |
|
|
194
|
+
| `mosul` | `شغموطة` → شرموطة (with `dialects: ['mosul']`) |
|
|
195
|
+
| `phrase` | `تعال العب بيه` (with `includePhrases`) |
|
|
196
|
+
| `split` | `ع*ي*ر`, `ع ي ر` (pieces joined again) |
|
|
197
|
+
| `symbols` | `g@hba`, `$harmoota` (@ as a, $ as s) |
|
|
198
|
+
|
|
199
|
+
Including the output of `inspect` in a [“Clean word flagged” issue](https://github.com/karam-fikre/iraqi-dialect-bad-words/issues/new?template=false-positive.yml) shows exactly which entry matched and which rules it needed; the issue form has a field for it.
|
|
115
200
|
|
|
116
201
|
### `addWords(words: string[]): void`
|
|
117
202
|
|
|
118
203
|
Adds words to this filter.
|
|
119
204
|
|
|
205
|
+
### `addSafeWords(words: string[]): void`
|
|
206
|
+
|
|
207
|
+
Adds safe stems to this filter, like the `safeWords` option.
|
|
208
|
+
|
|
120
209
|
### `removeWords(words: string[]): void`
|
|
121
210
|
|
|
122
211
|
Removes words from this filter, for example a built-in word you don't want flagged in your app.
|
|
123
212
|
|
|
213
|
+
### `addPhrases(phrases: string[]): void` / `removePhrases(phrases: string[]): void`
|
|
214
|
+
|
|
215
|
+
Adds phrases to this filter, like the `customPhrases` option, or removes them, including built-in ones.
|
|
216
|
+
|
|
217
|
+
A phrase is its words separated by spaces. A word can list alternatives separated by `|`, so one entry covers every combination:
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
[
|
|
221
|
+
"قوم|كوم بيه|بينو"
|
|
222
|
+
]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
matches `قوم بيه`, `قوم بينو`, `كوم بيه` and `كوم بينو`. A phrase matches when its words appear one after another with only spaces or punctuation between them (`تعال، العب بيه!`). Each word is normalized like any other, so `گوم` matches the `قوم` alternative without listing it. The first word can carry an attached prefix (`وتعال العب بيه`) or the Iraqi progressive `د` (`دقوم بيه`). Other forms aren't stripped or guessed, so list each one you want as an alternative.
|
|
226
|
+
|
|
227
|
+
The whole phrase is reported as one match with the rule `phrase`, its `entry` being the line as listed, and `clean()` masks all of it. `removePhrases()` takes a line as listed; the order of its alternatives doesn't matter.
|
|
228
|
+
|
|
124
229
|
## What it catches
|
|
125
230
|
|
|
126
231
|
Matching is done per whole word, after normalizing both the text and the word list, so these all match the same entry:
|
|
@@ -132,19 +237,28 @@ Matching is done per whole word, after normalizing both the text and the word li
|
|
|
132
237
|
| Tatweel | `عـــارة` |
|
|
133
238
|
| ة / ه, ى / ي, أ / إ / آ / ا | `عاره` |
|
|
134
239
|
| Persian/Kurdish keyboard letters ی ک ە | `عیر`, `کس` |
|
|
240
|
+
| گ read as ق (Iraqi) and as ك (Kurdish typists) | `گحبة` = `قحبة`, `گس` = `كس` |
|
|
135
241
|
| Invisible characters hidden inside a word (zero-width, direction marks, soft hyphen) | `عارة` |
|
|
136
|
-
|
|
|
242
|
+
| Letters split by symbols, or by spaces between single letters | `ع*ي*ر`, `ع.ي.ر`, `ع ي ر`, `عي * ر`, `شرم.وطة`, `g-a-h-b-a` |
|
|
243
|
+
| Arabic presentation forms, full-width and mathematical letters | `ﻋﻴﺮ`, `gahba`, `𝐠𝐚𝐡𝐛𝐚` |
|
|
244
|
+
| Greek or Cyrillic look-alike letters in a Latin word | `gаhba` (Cyrillic `а`) |
|
|
245
|
+
| Attached prefixes و ف ب ل ك ال, and يا / ويا | `والعارة`, `للعارة`, `ياعارة`, `يالعارة`, `وياعارة` |
|
|
137
246
|
| Pronoun suffixes (with `stripSuffixes`) | `طيزهم`, `وطيزكم` |
|
|
138
247
|
| Punctuation next to the word | `عارة!` `عارة،` |
|
|
139
248
|
| Digits glued to or inside an Arabic word | `عارة3`, `عا٣رة` |
|
|
140
|
-
| Arabizi (Latin letters and numbers, Western or Arabic-Indic), including spelling variants | `ga7ba` = `ga٧ba` = `qahba` = `gahba`, `kosomak` = `kusumak`, `3ayri` = `ayri` |
|
|
249
|
+
| Arabizi (Latin letters and numbers, Western or Arabic-Indic), including spelling variants | `ga7ba` = `ga٧ba` = `qahba` = `gahba`, `kosomak` = `kusumak`, `3ayri` = `ayri`, `6eezak` = `teezak` (6 = ط), `9awad` = `8awad` = `gawad` (9, 8 = ق) |
|
|
250
|
+
| @ and $ standing in for letters in Arabizi | `g@hba`, `$harmoota` |
|
|
141
251
|
|
|
142
252
|
Because matching is whole-word, a clean word that merely contains a bad word is not flagged.
|
|
143
253
|
|
|
254
|
+
Split words are found by joining neighbouring pieces when the gap between them is short, on one line, and either has no spaces (`عي*ر`), sits between single letters (`ع ي ر`), or has a symbol between pieces of one or two letters (`عي * ر`). Normal sentences like `انا و انت` or `د. علي` stay apart, and the joined word must match an entry exactly. A split safe stem stays safe: `ب ع ي ر` is بعير (camel).
|
|
255
|
+
|
|
144
256
|
## Known limitations
|
|
145
257
|
|
|
146
|
-
- Phrases (
|
|
258
|
+
- Phrases match only as listed, word for word: another word in between (`تعال هنا العب بيه`) or an unlisted form (`بيهم`) isn't matched.
|
|
147
259
|
- Words glued together without a space (e.g. the same word typed twice) aren't detected.
|
|
260
|
+
- A letter replaced by a symbol (`ع*ر`, `ك*بة`) isn't detected: the symbol could stand for any letter, and `ع*ر` could just as well be عمر.
|
|
261
|
+
- Longer pieces split with spaces around the symbol (`شرم * وطة`) aren't joined, so normal sentences aren't either.
|
|
148
262
|
- No word list is complete. Use this as one layer of moderation, not the only one.
|
|
149
263
|
|
|
150
264
|
## Contributing
|
|
@@ -154,10 +268,11 @@ The word list is the heart of this package, and native speakers are the best jud
|
|
|
154
268
|
- **A word is missing:** open a [“Suggest a word” issue](https://github.com/karam-fikre/iraqi-dialect-bad-words/issues/new?template=add-word.yml), or add it to [`src/words.json`](src/words.json) in a pull request.
|
|
155
269
|
- **A clean word is flagged:** open a [“Clean word flagged” issue](https://github.com/karam-fikre/iraqi-dialect-bad-words/issues/new?template=false-positive.yml).
|
|
156
270
|
If it's flagged only because of a prefix (e.g. `بعير` "camel" = `ب` + a bad word), the fix is adding it to [`src/safe-stems.json`](src/safe-stems.json).
|
|
271
|
+
A safe stem written with گ protects only words spelled with گ, so `گلب` ("heart", Iraqi spelling of قلب) can be safe while `كلب` is not.
|
|
157
272
|
|
|
158
|
-
- **A sentence is judged wrong:** add it to [`test/fixtures/sentences.json`](test/fixtures/sentences.json) with the verdict a native speaker expects (`"profane": true` or `false`, plus `"options"` if it needs `includeInsults` or `stripSuffixes`). Every sentence there runs as a test, so later word-list changes can't silently flip it.
|
|
273
|
+
- **A sentence is judged wrong:** add it to [`test/fixtures/sentences.json`](test/fixtures/sentences.json) with the verdict a native speaker expects (`"profane": true` or `false`, plus `"options"` if it needs `includeInsults`, `includePhrases` or `stripSuffixes`). Every sentence there runs as a test, so later word-list changes can't silently flip it.
|
|
159
274
|
|
|
160
|
-
Words that also have a common, normal meaning (like كلب "dog") belong in [`src/insults.json`](src/insults.json), which is opt-in, not in `words.json`.
|
|
275
|
+
Words that also have a common, normal meaning (like كلب "dog") belong in [`src/insults.json`](src/insults.json), which is opt-in, not in `words.json`. Phrases of clean words that are vulgar together belong in [`src/phrases.json`](src/phrases.json), also opt-in.
|
|
161
276
|
|
|
162
277
|
### Development
|
|
163
278
|
|
|
@@ -166,8 +281,11 @@ npm install
|
|
|
166
281
|
npm test # run tests
|
|
167
282
|
npm run typecheck # type-check
|
|
168
283
|
npm run build # build dist/
|
|
284
|
+
npm run check -- article.txt --phrases # list every match in a text file
|
|
169
285
|
```
|
|
170
286
|
|
|
287
|
+
`npm run check` runs the filter over a text file (or stdin, e.g. `pbpaste | npm run check`) and prints each match with its line and column, the entry it matched, its source and rules, and the text around it. Flags turn on options: `--insults`, `--phrases`, `--suffixes`, `--mosul`, and `--clean` to also print the masked text. Running it on articles you know are clean is the quickest way to find false positives: each match is either a missing safe stem or a word that belongs in an opt-in list.
|
|
288
|
+
|
|
171
289
|
## License
|
|
172
290
|
|
|
173
291
|
[MIT](LICENSE)
|
package/dist/index.d.mts
CHANGED
|
@@ -1,20 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A rule a word needed to match its list entry, reported in ProfanityMatch.rules.
|
|
3
|
+
* Only rules that made a difference are reported: عارة matching عارة has none.
|
|
4
|
+
*/
|
|
5
|
+
type MatchRule = "unicode-forms" | "diacritics" | "invisible" | "spelling" | "keyboard" | "lookalike" | "arabic-digits" | "stretched" | "glued-digits" | "arabizi" | "gaf-as-qaf" | "gaf-as-kaf" | "prefix" | "suffix" | "mosul" | "split" | "symbols" | "phrase";
|
|
1
6
|
interface IraqiBadWordsFilterOptions {
|
|
2
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Extra words to filter, on top of the built-in list. Can be your own JSON
|
|
9
|
+
* word file: `customWords: require('./my-words.json')`.
|
|
10
|
+
*/
|
|
3
11
|
customWords?: string[];
|
|
12
|
+
/**
|
|
13
|
+
* Extra safe stems, on top of the built-in safe-stems.json: clean words that
|
|
14
|
+
* would otherwise match once a prefix or suffix is stripped, like the
|
|
15
|
+
* built-in بعير ("camel" = ب + عير). Their prefixed and suffixed forms are
|
|
16
|
+
* safe too (وبعيرك).
|
|
17
|
+
* Can be your own JSON file: `safeWords: require('./my-safe-words.json')`.
|
|
18
|
+
*/
|
|
19
|
+
safeWords?: string[];
|
|
4
20
|
/**
|
|
5
21
|
* Also filter milder insults (كلب, حمار, قندرة, …). Off by default because
|
|
6
22
|
* many of them are everyday words ("عندي كلب", "جلب لي هدية").
|
|
7
23
|
*/
|
|
8
24
|
includeInsults?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Also filter phrases (phrases.json) whose words are clean on their own but
|
|
27
|
+
* vulgar together (تعال العب بيه). Off by default because the same words can
|
|
28
|
+
* be meant literally ("come play with it").
|
|
29
|
+
*/
|
|
30
|
+
includePhrases?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Extra phrases to filter, whatever includePhrases is. Can be your own JSON
|
|
33
|
+
* file: `customPhrases: require('./my-phrases.json')`.
|
|
34
|
+
*/
|
|
35
|
+
customPhrases?: string[];
|
|
9
36
|
/**
|
|
10
37
|
* Also match words with a pronoun suffix the list doesn't spell out
|
|
11
38
|
* (طيزهم -> طيز). Off by default: it widens matching, so it can flag
|
|
12
39
|
* clean words that happen to end like a bad word plus a suffix.
|
|
13
40
|
*/
|
|
14
41
|
stripSuffixes?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Regional pronunciations to also match. "mosul": ر said as غ, so every entry
|
|
44
|
+
* and safe stem with ر also matches its غ form (عير -> عيغ, بعير -> بعيغ).
|
|
45
|
+
* The ر forms keep matching. Off by default.
|
|
46
|
+
*/
|
|
47
|
+
dialects?: Dialect[];
|
|
15
48
|
}
|
|
49
|
+
/** A regional pronunciation the filter can also match. */
|
|
50
|
+
type Dialect = "mosul";
|
|
16
51
|
/** Which list a dictionary entry came from. */
|
|
17
|
-
type WordSource = "words" | "insults" | "custom";
|
|
52
|
+
type WordSource = "words" | "insults" | "phrases" | "custom";
|
|
18
53
|
interface InspectOptions {
|
|
19
54
|
/**
|
|
20
55
|
* Also return words that matched but were cleared by a safe stem
|
|
@@ -24,7 +59,7 @@ interface InspectOptions {
|
|
|
24
59
|
}
|
|
25
60
|
/** A bad word found by inspect(). */
|
|
26
61
|
interface ProfanityMatch {
|
|
27
|
-
/** The word exactly as written in the input. */
|
|
62
|
+
/** The word (or phrase) exactly as written in the input. */
|
|
28
63
|
token: string;
|
|
29
64
|
/**
|
|
30
65
|
* Offset of the word in the input; text.slice(start, end) === token.
|
|
@@ -33,9 +68,15 @@ interface ProfanityMatch {
|
|
|
33
68
|
start: number;
|
|
34
69
|
/** Offset just past the end of the word in the input. */
|
|
35
70
|
end: number;
|
|
36
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* The word after normalization (diacritics, stretched letters, … removed).
|
|
73
|
+
* For a word with گ, the reading that matched: گ as ق or as ك.
|
|
74
|
+
*/
|
|
37
75
|
normalized: string;
|
|
38
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* The dictionary entry (word or phrase) it matched, spelled as in its list. For a Mosul form
|
|
78
|
+
* (rule "mosul"), the listed ر spelling: الشرموطة for الشغموطة.
|
|
79
|
+
*/
|
|
39
80
|
entry: string;
|
|
40
81
|
/** The list the entry came from. */
|
|
41
82
|
source: WordSource;
|
|
@@ -43,8 +84,10 @@ interface ProfanityMatch {
|
|
|
43
84
|
prefix: string;
|
|
44
85
|
/** The suffix stripped before matching (only with stripSuffixes), or "". */
|
|
45
86
|
suffix: string;
|
|
87
|
+
/** The rules the word needed to match the entry, e.g. ["prefix", "arabizi"]. */
|
|
88
|
+
rules: MatchRule[];
|
|
46
89
|
/**
|
|
47
|
-
* The safe stem that cleared this match, spelled as in safe-stems.json.
|
|
90
|
+
* The safe stem that cleared this match, spelled as in safe-stems.json or safeWords.
|
|
48
91
|
* Only set on matches returned with `includeSuppressed`; these are not profane.
|
|
49
92
|
*/
|
|
50
93
|
suppressedBy?: string;
|
|
@@ -53,42 +96,135 @@ declare class IraqiBadWordsFilter {
|
|
|
53
96
|
private words;
|
|
54
97
|
private safeStems;
|
|
55
98
|
private stripSuffixes;
|
|
99
|
+
private mosul;
|
|
100
|
+
private entryStarts;
|
|
101
|
+
private longest;
|
|
102
|
+
private normalized;
|
|
103
|
+
private phrases;
|
|
56
104
|
constructor(options?: string[] | IraqiBadWordsFilterOptions);
|
|
57
105
|
/**
|
|
58
|
-
* Normalizes
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* (عاااارة -> عارة).
|
|
106
|
+
* Normalizes a word or token for comparison: removes diacritics, tatweel and
|
|
107
|
+
* invisible characters, unifies letter variants (أ/ا, ة/ه, ی/ي, ک/ك, …),
|
|
108
|
+
* reads Arabic-Indic digits, collapses stretched letters (عاااارة -> عارة),
|
|
109
|
+
* ignores digits in Arabic words and folds Arabizi. See NORMALIZE_STEPS.
|
|
63
110
|
*/
|
|
64
|
-
private
|
|
111
|
+
private normalize;
|
|
65
112
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* teezak / tizak. Only words made purely of Latin letters and digits are
|
|
69
|
-
* folded; expects text already passed through normalizeArabic.
|
|
113
|
+
* The ways a normalized word can be read. Iraqi Arabic writes ق as گ, but
|
|
114
|
+
* Kurdish typists sometimes use گ for ك, so a word with گ is tried both ways.
|
|
70
115
|
*/
|
|
71
|
-
private
|
|
72
|
-
/**
|
|
73
|
-
private
|
|
116
|
+
private readings;
|
|
117
|
+
/** A list word plus its regional forms (عير -> عير, عيغ with the Mosul dialect). */
|
|
118
|
+
private dialectForms;
|
|
119
|
+
/** The key a list word is stored under. A گ in a list entry means ق. */
|
|
120
|
+
private entryKey;
|
|
121
|
+
/**
|
|
122
|
+
* A word list with surrounding whitespace trimmed and entries that are empty
|
|
123
|
+
* after normalization skipped. A stray "" in a user's file would otherwise
|
|
124
|
+
* match a lone tatweel as a word, or clear every match as a safe stem.
|
|
125
|
+
*/
|
|
126
|
+
private cleanList;
|
|
127
|
+
/**
|
|
128
|
+
* Adds safe stems from one list. A safe stem written with گ protects only
|
|
129
|
+
* words spelled with گ (گلب "heart"), so it isn't read as ق or ك.
|
|
130
|
+
*/
|
|
131
|
+
private addSafeStems;
|
|
74
132
|
/**
|
|
75
133
|
* Adds words from one list. A word already present keeps its first source.
|
|
76
134
|
*/
|
|
77
135
|
private addEntries;
|
|
136
|
+
/**
|
|
137
|
+
* Reads a listed phrase: words separated by spaces, each with its alternatives
|
|
138
|
+
* separated by | (قوم|كوم بيه|بينو). Punctuation in a word is ignored, and
|
|
139
|
+
* alternatives that are empty after normalization (a lone tatweel) are left
|
|
140
|
+
* out. With the Mosul dialect, an alternative with ر also has its غ form.
|
|
141
|
+
* Phrases that match the same text get the same id.
|
|
142
|
+
*/
|
|
143
|
+
private parsePhrase;
|
|
144
|
+
/**
|
|
145
|
+
* Adds phrases from one list, indexed by each alternative of their first word.
|
|
146
|
+
* A phrase already present keeps its first source.
|
|
147
|
+
*/
|
|
148
|
+
private addPhraseEntries;
|
|
149
|
+
/**
|
|
150
|
+
* The first word of a phrase with attached prefixes removed, and also the
|
|
151
|
+
* Iraqi progressive د (دقوم بيه, ودقوم بيه).
|
|
152
|
+
*/
|
|
153
|
+
private phraseCandidates;
|
|
78
154
|
/**
|
|
79
155
|
* Checks a single word, also trying it with attached prefixes removed
|
|
80
156
|
* (والكحبة -> كحبة, بطيزك -> طيزك), and suffixes removed when stripSuffixes is on.
|
|
81
157
|
* Returns the matched entry, or null.
|
|
82
158
|
* A match cleared by a safe stem is returned with `suppressedBy` set.
|
|
159
|
+
* Pass `spelled` when the token is already normalized.
|
|
83
160
|
*/
|
|
84
161
|
private matchWord;
|
|
85
162
|
/**
|
|
86
|
-
*
|
|
163
|
+
* Works out which rules a match needed, by comparing the typed word with the
|
|
164
|
+
* listed spelling (plus any stripped prefix and suffix). A normalization rule
|
|
165
|
+
* counts only if leaving it out would keep them apart: عاااارة vs عارة needs
|
|
166
|
+
* "stretched" but not "spelling", since both have ة.
|
|
167
|
+
*/
|
|
168
|
+
private explain;
|
|
169
|
+
/** The word plus each form of it with an attached prefix removed. */
|
|
170
|
+
private prefixCandidates;
|
|
171
|
+
/** The first safe stem one of the candidates starts with, as listed. */
|
|
172
|
+
private findSafe;
|
|
173
|
+
/** Matches one normalized reading of a word. */
|
|
174
|
+
private matchReading;
|
|
175
|
+
/**
|
|
176
|
+
* Finds every match in the text, including ones cleared by a safe stem: whole
|
|
177
|
+
* words, phrases, words split into pieces, and Arabizi words with symbols as
|
|
178
|
+
* letters. Matches from different passes can overlap; inspect() resolves that.
|
|
179
|
+
*/
|
|
180
|
+
private findMatches;
|
|
181
|
+
/**
|
|
182
|
+
* Finds listed phrases: their words one after another, with only spaces or
|
|
183
|
+
* punctuation between them, each one of its alternatives. Each word is
|
|
184
|
+
* normalized like any other, and the first can carry an attached prefix
|
|
185
|
+
* (وتعال العب بيه) or the progressive د (دقوم بيه). The longest phrase wins.
|
|
186
|
+
*/
|
|
187
|
+
private findPhraseMatches;
|
|
188
|
+
/**
|
|
189
|
+
* Matches each word in the text.
|
|
87
190
|
* A token with invisible characters is matched whole first (عارة -> عارة);
|
|
88
191
|
* if that finds nothing profane, each visible piece is tried on its own, so an
|
|
89
192
|
* invisible character between two words can't hide a bad word either.
|
|
90
193
|
*/
|
|
91
|
-
private
|
|
194
|
+
private findWordMatches;
|
|
195
|
+
/**
|
|
196
|
+
* Whether two neighbouring pieces can be one split word. The gap between them
|
|
197
|
+
* must be short and on one line, and either have no spaces (عي*ر, شرم.وطة),
|
|
198
|
+
* sit between single letters (ع ي ر), or have a symbol between pieces of at
|
|
199
|
+
* most two letters (عي * ر). Normal sentences (انا و انت, كلمة، كلمة) stay apart.
|
|
200
|
+
* `size` gives a piece's normalized length, worked out only when needed.
|
|
201
|
+
*/
|
|
202
|
+
private joinable;
|
|
203
|
+
/**
|
|
204
|
+
* Whether a normalized word, or a longer one made by joining more pieces to
|
|
205
|
+
* it, could still match an entry: it must be the start of a prefix, or a
|
|
206
|
+
* prefix plus the start of an entry (or of an entry and a suffix). A word
|
|
207
|
+
* with no letters yet (digits only) always could.
|
|
208
|
+
*/
|
|
209
|
+
private couldMatch;
|
|
210
|
+
/**
|
|
211
|
+
* Finds bad words split into pieces by symbols or spaces (ع*ي*ر, ع ي ر,
|
|
212
|
+
* g-a-h-b-a) by joining neighbouring pieces again. From each piece, the
|
|
213
|
+
* longest joined stretch that matches wins, and its span covers the separators.
|
|
214
|
+
*/
|
|
215
|
+
private findSplitMatches;
|
|
216
|
+
/**
|
|
217
|
+
* Finds Arabizi words with @ or $ standing in for a letter (g@hba, $harmoota).
|
|
218
|
+
* A word glued to other letters, like an Arabic word, is left to the other passes.
|
|
219
|
+
*/
|
|
220
|
+
private findSymbolMatches;
|
|
221
|
+
/**
|
|
222
|
+
* Puts matches in text order and drops overlapping ones, so each span is
|
|
223
|
+
* reported and masked once. Real matches go before ones cleared by a safe
|
|
224
|
+
* stem, then earlier and longer before later or shorter: ع*ي*ر is one match,
|
|
225
|
+
* not also its pieces.
|
|
226
|
+
*/
|
|
227
|
+
private resolveOverlaps;
|
|
92
228
|
/**
|
|
93
229
|
* Checks if a given text contains profane words.
|
|
94
230
|
*/
|
|
@@ -106,10 +242,23 @@ declare class IraqiBadWordsFilter {
|
|
|
106
242
|
* Dynamically add words to the active dictionary.
|
|
107
243
|
*/
|
|
108
244
|
addWords(newWords: string[]): void;
|
|
245
|
+
/**
|
|
246
|
+
* Add safe stems to this filter, like the safeWords option.
|
|
247
|
+
*/
|
|
248
|
+
addSafeWords(newWords: string[]): void;
|
|
109
249
|
/**
|
|
110
250
|
* Remove words from the active filter dictionary.
|
|
111
251
|
*/
|
|
112
252
|
removeWords(wordsToRemove: string[]): void;
|
|
253
|
+
/**
|
|
254
|
+
* Add phrases to this filter, like the customPhrases option.
|
|
255
|
+
*/
|
|
256
|
+
addPhrases(newPhrases: string[]): void;
|
|
257
|
+
/**
|
|
258
|
+
* Remove phrases from this filter, for example a built-in phrase you don't want
|
|
259
|
+
* flagged. Pass the phrase as listed, alternatives included.
|
|
260
|
+
*/
|
|
261
|
+
removePhrases(phrasesToRemove: string[]): void;
|
|
113
262
|
}
|
|
114
263
|
|
|
115
|
-
export { type InspectOptions, IraqiBadWordsFilter, type IraqiBadWordsFilterOptions, type ProfanityMatch, type WordSource, IraqiBadWordsFilter as default };
|
|
264
|
+
export { type Dialect, type InspectOptions, IraqiBadWordsFilter, type IraqiBadWordsFilterOptions, type MatchRule, type ProfanityMatch, type WordSource, IraqiBadWordsFilter as default };
|