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