diff --git a/badword.ts b/badword.ts new file mode 100644 index 0000000..75d34f6 --- /dev/null +++ b/badword.ts @@ -0,0 +1,275 @@ +function RegexMatch(word: string, regex: RegExp): boolean { + const words = word.trim(); + const barisdata: string[] = []; + + if (words) { + for (let index = 0; index < words.length; index++) { + const datacocok = barisdata.join("").toLowerCase(); + + if (datacocok.match(regex)) { + return true; + } else { + let modifiedWord = words[index]; + + if (modifiedWord.includes("3")) { + modifiedWord = modifiedWord.replace("3", "e"); + } else if (modifiedWord.includes("0")) { + modifiedWord = modifiedWord.replace("0", "o"); + } else if (modifiedWord.includes("4")) { + modifiedWord = modifiedWord.replace("4", "a"); + } else if (modifiedWord.includes("5")) { + modifiedWord = modifiedWord.replace("5", "s"); + } else if (modifiedWord.includes("8")) { + modifiedWord = modifiedWord.replace("8", "b"); + } + + if (datacocok.replace(/1/gi, "i").match(regex) || + datacocok.replace(/1/gi, "l").match(regex) || + datacocok.replace(/6/gi, "b").match(regex) || + datacocok.replace(/6/gi, "g").match(regex)) { + return true; + } + } + + barisdata.push(words[index].trim()); + } + } + + return false; +} + +function escapeRegExp(strings: string): string { + const data = strings.trim().toLowerCase().split("|").filter(Boolean); + + for (let index = 0; index < data.length; index++) { + const element = data[index]; + + if (!((element.includes("(") && element.includes(")")) || + (element.includes("[") && element.includes("]")))) { + data[index] = data[index] + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/[a4]/g, "[a4]") + .replace(/[s5]/g, "[s5]") + .replace("i", "[i1]") + .replace("l", "[l1]") + .replace(/[o0]/g, "[o0]") + .replace(/[e3]/g, "[e3]") + .replace(/[b8]/g, "[b8]") + .replace(/[kx]/g, "[kx]"); + } + } + + return new RegExp(data.join("|")).source; +} + +function validateInput(type: string, value: string): boolean { + let regex: RegExp; + + switch (type) { + case 'email': + regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.(com|net|org|edu|gov|mil|co|info|io|biz|id|us|uk|ca|au|de|fr|es|it|jp|cn|br|in|ru|mx|kr|za|nl|se|no|fi|dk|pl|pt|ar|ch|hk|sg|my|th|vn|ae|at|be|cz|hu|ro|bg|gr|lt|lv|sk|si|ee|cy)(\.[a-zA-Z]{2,})?$/; + break; + case 'phone': + regex = /^(?:\+?(\d{1,3}))?[-. ]?(\(?\d{1,4}?\)?)[-.\s]?(\d{1,4})[-.\s]?(\d{1,4})[-.\s]?(\d{1,9})$/; + break; + case 'url': + regex = /^(https?:\/\/)?(www\.)?([a-zA-Z0-9-]+\.[a-zA-Z]{2,})(\/[^\s]*)?$/; + break; + default: + return false; // Invalid type + } + + return regex.test(value); +} + +class FilterBadWord { + protected _text: string; + protected _filt: RegExp; + protected _subfilter: RegExp; + protected __subtxic: [string, string][]; + protected _st: boolean; + + constructor(text: string = "", customFilter: string = "", customSubFilter: string = "") { + this._text = text; + this._filt = /b[a4][s5]hfu[l1][l1]|k[i1][l1][l1]|fuck[*]?|dr[uo]g[*]?|d[i1]ck[*]?|[a4][s5][s5]|[l1][i1]p|pu[s5][s5]y[*]?|fk/gi; + this._subfilter = /[a4][s5][s5]|[l1][i1]p|pu[s5][s5]y[*]?|[s5]uck[*]?|m[o0]th[e3]r[*]?|m[o0]m[*]?|d[o0]g[*]?|l[o0]w[*]?|s[e3]x[*]?/gi; + + if (customFilter.length > 3) { + this._filt = new RegExp(this._filt.source + "|" + escapeRegExp(customFilter), "gi"); + } + + if (customSubFilter.length > 3) { + this._subfilter = new RegExp(this._subfilter.source + "|" + escapeRegExp(customSubFilter), "gi"); + } + + this.__subtxic = []; + this._st = false; + } + + private getBoundPosition(word: string, position: number): string { + let paragraph = word; + + while (position > 0 && paragraph[position] === " ") position--; + + position = paragraph.lastIndexOf(" ", position) + 1; + let end = paragraph.indexOf(" ", position); + + if (end === -1) { + end = paragraph.length; + } + + return paragraph.substring(position, end); + } + + private positionStatic(word: string, filters: RegExp): number[] { + const wordlist_ = word.toLowerCase().split(' '); + const positions: number[] = []; + + wordlist_.forEach((word, index) => { + const pos = index && wordlist_[index - 1].length + positions.length + 1; + + if (word.match(filters) || RegexMatch(word, filters)) { + positions.push(pos); + } + }); + + return positions; + } + + public position(): number[] { + return this.positionStatic(this._text.toString(), this._filt); + } + + public get thisToxic(): (string | number)[] | false { + const check = this.position(); + const arry: (string | number)[] = []; + + if (check.length > 0) { + const word = this._text.toLowerCase(); + function before_str(number , key){ + + return word.substring(number, word.indexOf(key));//nomer dan keyword + + }; + + function after_str(w, spec){ + let data =word.substring( word.indexOf(w), spec.length+word.length ); + return data.replace(w, "").trim(); //, word.indexOf(spec)); + }; + + for (const index of check) { + const word_s = this.getBoundPosition(this._text.toLowerCase(), index); + const before = word.substring(0, word.indexOf(word_s)).trim().split(" "); + const after = word.substring(word.indexOf(word_s) + word_s.length).trim().split(" "); + + // Check before + before.forEach(d => { + if (d.match(this._subfilter)) { + this.__subtxic.push([d, '*'.repeat(d.length)]); + } + }); + + // Check after + after.forEach(d => { + if (d.match(this._subfilter)) { + this.__subtxic.push([d, '*'.repeat(d.length)]); + } + }); + + // Add toxic word if found in before or after + if (before.length && before[before.length - 1].match(this._subfilter)) { + arry.push("Toxic", 1, before[before.length - 1]); + return arry; + } + + if (after.length && after[0].match(this._subfilter)) { + arry.push("Toxic", 1, after[0]); + return arry; + } + + if (after.length > 1 && after[1].match(this._subfilter)) { + arry.push("Toxic", 1, after[1]); + return arry; + } + } + + arry.push("Notoxic", 0); + return arry; + } + + return false; + } + + set thisToxic(key: any) { + throw key; + } + + public clean(position: number[]): string { + let words = this._text.split(" "); + const sensor = "*"; + + position.forEach((number) => { + const getWord = this.getBoundPosition(this._text, number); + words = words.map(word => word.replace(getWord, sensor.repeat(getWord.length))); + }); + this.__subtxic.forEach(([oldWord, newWord]) => { + words = words.map(word => { + return !(validateInput("email", word) || validateInput("url", word)) && this._st + ? word.replace(oldWord, newWord): word; + }); + }); + return words.join(" "); + } +} + +class filters_badword extends FilterBadWord { + protected _cl: boolean; + protected _st: boolean; + + public text_o(text: string): void { + this._text = text.toString(); + } + + public config(cl: boolean = true, smart: boolean = true, customFilter: string = "", customSubFilter: string = ""): void { + this._cl = cl; + this._st = smart; + + if (customFilter.length > 3) { + this._filt = new RegExp(this._filt.source + "|" + escapeRegExp(customFilter), "gi"); + } + if (customSubFilter.length > 3) { + this._subfilter = new RegExp(this._subfilter.source + "|" + escapeRegExp(customSubFilter), "gi"); + } + } + + public get cleans(): string { + if (this._cl) { + if (this.thisToxic){ + if (this.thisToxic[1] === 1 && this.thisToxic.length > 2) { + return this.clean(this.position()); + }; + }; + return this.clean(this.position()); + }; + return this._text.trim(); + } + + set cleans(value: string) { + throw value; + } +} + +export { + /** + * FilterBadWord class: class for filtering bad words + *@param {string} text - The text to filter + *@param {string} customFilter - List of bad words + *@param {string} customSubFilter - List of bad sub words + */ + FilterBadWord, + /** + * filters_badword class: a simpler class to filter bad words + * which uses the FilterBadWord class. To use it you have to call the config function + */ + filters_badword +};