Ran black code formatter on everything.
[python_utils.git] / profanity_filter.py
1 #!/usr/bin/env python3
2
3 import logging
4 import random
5 import re
6 import string
7 import sys
8
9 import nltk
10 from nltk.stem import PorterStemmer
11
12 import decorator_utils
13 import string_utils
14
15
16 logger = logging.getLogger(__name__)
17
18
19 @decorator_utils.singleton
20 class ProfanityFilter(object):
21     def __init__(self):
22         self.bad_words = set(
23             [
24                 'acrotomophilia',
25                 'anal',
26                 'analingus',
27                 'anally',
28                 'anilingus',
29                 'anus',
30                 'arsehol',
31                 'arsehole',
32                 'ass',
33                 'asses',
34                 'asshol',
35                 'asshole',
36                 'assmunch',
37                 'auto erot',
38                 'auto erotic',
39                 'autoerotic',
40                 'babeland',
41                 'babi batter',
42                 'baby batter',
43                 'ball gag',
44                 'ball gravi',
45                 'ball gravy',
46                 'ball kick',
47                 'ball kicking',
48                 'ball lick',
49                 'ball licking',
50                 'ball sack',
51                 'ball suck',
52                 'ball sucking',
53                 'ball zack',
54                 'bangbro',
55                 'bangbros',
56                 'bare legal',
57                 'bareback',
58                 'barely legal',
59                 'barenak',
60                 'barenaked',
61                 'bastardo',
62                 'bastinado',
63                 'bbc',
64                 'bbw',
65                 'bdsm',
66                 'beaver cleaver',
67                 'beaver lip',
68                 'beaver lips',
69                 'bestial',
70                 'bestiality',
71                 'bi curiou',
72                 'bi curious',
73                 'big black',
74                 'big breasts',
75                 'big knocker',
76                 'big knockers',
77                 'big tit',
78                 'big tits',
79                 'bimbo',
80                 'birdlock',
81                 'bitch',
82                 'bitches',
83                 'black cock',
84                 'blond action',
85                 'blond on blond',
86                 'blonde action',
87                 'blow j',
88                 'blow job',
89                 'blowjob',
90                 'blow my',
91                 'blow me',
92                 'blow ourselv',
93                 'blow ourselves',
94                 'blow your load',
95                 'blue waffl',
96                 'blue waffle',
97                 'blumpkin',
98                 'bollock',
99                 'bollocks',
100                 'bondag',
101                 'bondage',
102                 'boner',
103                 'boob',
104                 'boobs',
105                 'booti call',
106                 'booty call',
107                 'breast',
108                 'breasts',
109                 'brown shower',
110                 'brown showers',
111                 'brunett action',
112                 'brunette action',
113                 'bukkak',
114                 'bukkake',
115                 'bulldyk',
116                 'bulldyke',
117                 'bullet vibe',
118                 'bullshit',
119                 'bung hole',
120                 'bunghol',
121                 'bunghole',
122                 'busti',
123                 'busty',
124                 'butt',
125                 'buttcheek',
126                 'buttcheeks',
127                 'butthol',
128                 'butthole',
129                 'camel toe',
130                 'camgirl',
131                 'camslut',
132                 'camwhore',
133                 'carpet muncher',
134                 'carpetmuncher',
135                 'chocol rosebud',
136                 'chocolate rosebuds',
137                 'circlejerk',
138                 'chink',
139                 'cleveland steamer',
140                 'clit',
141                 'clitori',
142                 'clitoris',
143                 'clover clamp',
144                 'clover clamps',
145                 'clusterfuck',
146                 'cock',
147                 'cocks',
148                 'coprolagnia',
149                 'coprophilia',
150                 'cornhol',
151                 'cornhole',
152                 'cream pie',
153                 'creampi',
154                 'creampie',
155                 'cum',
156                 'cumming',
157                 'cunnilingu',
158                 'cunnilingus',
159                 'cunt',
160                 'damn',
161                 'darki',
162                 'darkie',
163                 'date rape',
164                 'daterap',
165                 'daterape',
166                 'deep throat',
167                 'deepthroat',
168                 'dick',
169                 'dildo',
170                 'dirti pillow',
171                 'dirti sanchez',
172                 'dirty pillow',
173                 'dirty sanchez',
174                 'dog style',
175                 'doggi style',
176                 'doggie style',
177                 'doggiestyl',
178                 'doggiestyle',
179                 'doggystyle',
180                 'dolcett',
181                 'domination',
182                 'dominatrix',
183                 'domm',
184                 'dommes',
185                 'donkey punch',
186                 'doubl dick',
187                 'doubl dong',
188                 'doubl penetr',
189                 'double dick',
190                 'double dong',
191                 'double penetration',
192                 'dp action',
193                 'dtf',
194                 'eat my ass',
195                 'ecchi',
196                 'ejacul',
197                 'erection',
198                 'erotic',
199                 'erotism',
200                 'escort',
201                 'ethical slut',
202                 'eunuch',
203                 'faggot',
204                 'fecal',
205                 'felch',
206                 'fellatio',
207                 'feltch',
208                 'female squirting',
209                 'femdom',
210                 'figging',
211                 'fingered',
212                 'fingering',
213                 'fingers',
214                 'fisted',
215                 'fisting',
216                 'fists',
217                 'foot fetish',
218                 'footjob',
219                 'frotting',
220                 'fuck button',
221                 'fuck',
222                 'fucked',
223                 'fucker',
224                 'fuckhead',
225                 'fuckin',
226                 'fucking',
227                 'fudge packer',
228                 'fudgepack',
229                 'fudgepacker',
230                 'futanari',
231                 'g spot',
232                 'g-spot',
233                 'gang bang',
234                 'gay sex',
235                 'gee spot',
236                 'genital',
237                 'giant cock',
238                 'girl gone wild',
239                 'girl on top',
240                 'girl on',
241                 'goatcx',
242                 'goatse',
243                 'goddamn',
244                 'gokkun',
245                 'golden shower',
246                 'goo girl',
247                 'goodpoop',
248                 'goregasm',
249                 'grope',
250                 'group sex',
251                 'gspot',
252                 'guro',
253                 'hand job',
254                 'handjob',
255                 'hard core',
256                 'hardcore',
257                 'hentai',
258                 'homoerotic',
259                 'honkey',
260                 'hooker',
261                 'horni',
262                 'horny',
263                 'hot chick',
264                 'how to kill',
265                 'how to murder',
266                 'huge fat',
267                 'humped',
268                 'humping',
269                 'humps',
270                 'incest',
271                 'intercourse',
272                 'jack off',
273                 'jail bait',
274                 'jailbait',
275                 'jerk off',
276                 'jigaboo',
277                 'jiggaboo',
278                 'jiggerboo',
279                 'jizz',
280                 'jugg',
281                 'kike',
282                 'kinbaku',
283                 'kinkster',
284                 'kinky',
285                 'knobbing',
286                 'leather restraint',
287                 'lemon party',
288                 'lolita',
289                 'lovemaking',
290                 'make me come',
291                 'male squirting',
292                 'masturb',
293                 'menage a trois',
294                 'milf',
295                 'missionary position',
296                 'motherfuck',
297                 'mound of venus',
298                 'mr hand',
299                 'muff diver',
300                 'muffdiv',
301                 'muffdiving',
302                 'nambla',
303                 'nawashi',
304                 'negro',
305                 'neonazi',
306                 'nig nog',
307                 'nigga',
308                 'nigger',
309                 'nimphomania',
310                 'nipple',
311                 'nip',
312                 'not safe for',
313                 'nsfl',
314                 'nsfw',
315                 'nude',
316                 'nudes',
317                 'nudity',
318                 'nut sack',
319                 'nutsack',
320                 'nympho',
321                 'nymphomania',
322                 'octopussy',
323                 'omorashi',
324                 'one night stand',
325                 'orgasm',
326                 'orgy',
327                 'paedophil',
328                 'paedophile',
329                 'panties',
330                 'panty',
331                 'pedobear',
332                 'pedophil',
333                 'pedophile',
334                 'pee',
335                 'pegging',
336                 'peni',
337                 'penis',
338                 'phone sex',
339                 'pigfucker',
340                 'piss pig',
341                 'piss',
342                 'pissing',
343                 'pisspig',
344                 'playboy',
345                 'pleasure chest',
346                 'pole smoker',
347                 'ponyplay',
348                 'poof',
349                 'poop chute',
350                 'poopchute',
351                 'porn',
352                 'pron',
353                 'pornhub',
354                 'porno',
355                 'pornographi',
356                 'pornography',
357                 'prince albert',
358                 'pthc',
359                 'pube',
360                 'pussi',
361                 'pussies',
362                 'pussy',
363                 'queaf',
364                 'queer',
365                 'raghead',
366                 'raging boner',
367                 'rape',
368                 'raping',
369                 'rapist',
370                 'rectum',
371                 'reverse cowgirl',
372                 'rimjob',
373                 'rimming',
374                 'rosy palm',
375                 'rusty trombone',
376                 's & m',
377                 's&m',
378                 's+m',
379                 'sadism',
380                 'scat',
381                 'schlong',
382                 'scissoring',
383                 'semen',
384                 'sex',
385                 'sexi',
386                 'sexo',
387                 'sexy',
388                 'shaved beaver',
389                 'shaved pussy',
390                 'shemale',
391                 'shibari',
392                 'shit',
393                 'shota',
394                 'shrimping',
395                 'slanteye',
396                 'slut',
397                 'smut',
398                 'snatch',
399                 'snm',
400                 'snowballing',
401                 'sodomi',
402                 'sodomize',
403                 'sodomy',
404                 'spic',
405                 'spooge',
406                 'spread legs',
407                 'squirting',
408                 'strap on',
409                 'strapon',
410                 'strappado',
411                 'strip club',
412                 'style doggy',
413                 'suck',
414                 'suicide girls',
415                 'sultry women',
416                 'swastika',
417                 'swinger',
418                 'taint',
419                 'tainted love',
420                 'taste my',
421                 'tea bagging',
422                 'threesome',
423                 'throating',
424                 'tied up',
425                 'tight white',
426                 'tit',
427                 'tits',
428                 'titti',
429                 'titties',
430                 'titty',
431                 'tongue in',
432                 'topless',
433                 'tosser',
434                 'towelhead',
435                 'tranny',
436                 'tribadism',
437                 'tub girl',
438                 'tubgirl',
439                 'tushy',
440                 'twat',
441                 'twink',
442                 'twinki',
443                 'twinkie',
444                 'undress',
445                 'upskirt',
446                 'urethra play',
447                 'urophilia',
448                 'vag',
449                 'vagina',
450                 'venus mound',
451                 'vibrator',
452                 'violet blue',
453                 'violet wand',
454                 'vorarephilia',
455                 'voyeur',
456                 'vulva',
457                 'wank',
458                 'wet dream',
459                 'wetback',
460                 'white power',
461                 'whore',
462                 'women rapping',
463                 'wrapping men',
464                 'wrinkled starfish',
465                 'xx',
466                 'xxx',
467                 'yaoi',
468                 'yellow shower',
469                 'yiffy',
470                 'zoophilia',
471             ]
472         )
473         self.stemmer = PorterStemmer()
474
475     def _normalize(self, text: str) -> str:
476         """Normalize text.
477
478         >>> _normalize('Tittie5')
479         'titties'
480
481         >>> _normalize('Suck a Dick!')
482         'suck a dick'
483
484         >>> _normalize('fucking a whore')
485         'fuck a whore'
486
487         """
488         result = text.lower()
489         result = result.replace("_", " ")
490         result = result.replace('0', 'o')
491         result = result.replace('1', 'l')
492         result = result.replace('4', 'a')
493         result = result.replace('5', 's')
494         result = result.replace('3', 'e')
495         for x in string.punctuation:
496             result = result.replace(x, "")
497         chunks = [
498             self.stemmer.stem(word) for word in nltk.word_tokenize(result)
499         ]
500         return ' '.join(chunks)
501
502     def tokenize(self, text: str):
503         for x in nltk.word_tokenize(text):
504             for y in re.split('\W+', x):
505                 yield y
506
507     def contains_bad_word(self, text: str) -> bool:
508         """Returns True if text contains a bad word (or more than one)
509         and False if no bad words were detected.
510
511         >>> contains_bad_word('fuck you')
512         True
513
514         >>> contains_bad_word('FucK u')
515         True
516
517         >>> contains_bad_word('FuK U')
518         False
519
520         """
521         words = [word for word in self.tokenize(text)]
522         for word in words:
523             if self.is_bad_word(word):
524                 logger.debug(f'"{word}" is profanity')
525                 return True
526
527         if len(words) > 1:
528             for bigram in string_utils.ngrams_presplit(words, 2):
529                 bigram = ' '.join(bigram)
530                 if self.is_bad_word(bigram):
531                     logger.debug(f'"{bigram}" is profanity')
532                     return True
533
534         if len(words) > 2:
535             for trigram in string_utils.ngrams_presplit(words, 3):
536                 trigram = ' '.join(trigram)
537                 if self.is_bad_word(trigram):
538                     logger.debug(f'"{trigram}" is profanity')
539                     return True
540         return False
541
542     def is_bad_word(self, word: str) -> bool:
543         return word in self.bad_words or self._normalize(word) in self.bad_words
544
545     def obscure_bad_words(self, text: str) -> str:
546         """Obscure bad words that are detected by inserting random punctuation
547         characters.
548
549         """
550
551         def obscure(word: str):
552             out = ''
553             last = ''
554             for letter in word:
555                 if letter.isspace():
556                     out += letter
557                 else:
558                     while True:
559                         char = random.choice(['#', '%', '!', '@', '&', '*'])
560                         if last != char:
561                             last = char
562                             out += char
563                             break
564             return out
565
566         words = self.tokenize(text)
567         words.append('')
568         words.append('')
569         words.append('')
570         out = ''
571
572         cursor = 0
573         while cursor < len(words) - 3:
574             word = words[cursor]
575             bigram = word + ' ' + words[cursor + 1]
576             trigram = bigram + ' ' + words[cursor + 2]
577             if self.is_bad_word(trigram):
578                 out += obscure(trigram) + ' '
579                 cursor += 3
580             elif self.is_bad_word(bigram):
581                 out += obscure(bigram) + ' '
582                 cursor += 2
583             elif self.is_bad_word(word):
584                 out += obscure(word) + ' '
585                 cursor += 1
586             else:
587                 out += word + ' '
588                 cursor += 1
589         return out.strip()
590
591
592 def main() -> None:
593     import doctest
594
595     doctest.testmod()
596     pf = ProfanityFilter()
597     phrase = ' '.join(sys.argv[1:])
598     print(pf.contains_bad_word(phrase))
599     print(pf.obscure_bad_words(phrase))
600     sys.exit(0)
601
602
603 if __name__ == '__main__':
604     main()