Skip to content

Commit e569fd3

Browse files
Revert back to previous version
CensorCore v3.0.0 is currently not able to run. v3 was supposed to use a Python script to filter messages.
1 parent 711f555 commit e569fd3

1 file changed

Lines changed: 117 additions & 36 deletions

File tree

CensorCore.js

Lines changed: 117 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,95 @@
11
// CensorCore.js
2-
// Copyright (c) 2026 Derrick Richard
2+
// Copyright (c) 2025 Derrick Richard
33
// Lightweight, zero-setup message filtering library.
44
// Source: https://github.com/DerrickRichard/CensorCore-Library
5-
// Source: https://github.com/DerrickRichard/CensorCore-Moderation-API
65
// Licensed under the MIT License
76
// Author: Derrick Richard (https://derrickrichard.github.io/profile/)
87
// Weekly programming articles: https://dev.to/derrickrichard
9-
// v3.0 — Switched to a Python API to detect profane phrases. Removed the wordlist.json file.
8+
// v2.0 — Adds severity levels, phrase detection, custom rules, async events, and rich analyze() API.
109

1110
(function () {
11+
let rules = [];
1212
let ready = false;
1313
let loadFailed = false;
1414
const readyCallbacks = [];
1515
const errorCallbacks = [];
1616

17-
// URL of your moderation API
18-
const API_URL = "http://localhost:8000/analyze"; // Change for production
17+
// Default severity mapping for categories
18+
const CATEGORY_SEVERITY = {
19+
profanity: "medium",
20+
hate_speech: "high",
21+
harassment: "medium",
22+
sexual_content: "high",
23+
violence: "high",
24+
self_harm: "high",
25+
drugs: "medium",
26+
weapons: "high",
27+
extremism: "high",
28+
terrorism: "high",
29+
disallowed_phrases: "medium",
30+
custom: "low",
31+
default: "low"
32+
};
1933

2034
// Public API
2135
const censor = {
22-
async isBlocked(text) {
23-
const result = await this.analyze(text);
24-
return result.blocked;
36+
isBlocked(text) {
37+
return this.analyze(text).blocked;
2538
},
2639

27-
async analyze(text) {
40+
analyze(text) {
2841
if (!ready || !text) {
2942
return { blocked: false, matches: [] };
3043
}
3144

32-
try {
33-
const response = await fetch(API_URL, {
34-
method: "POST",
35-
headers: { "Content-Type": "application/json" },
36-
body: JSON.stringify({ text })
37-
});
45+
const normalized = normalizeText(text);
46+
const matches = [];
3847

39-
if (!response.ok) {
40-
throw new Error("API returned non-OK status");
48+
for (const rule of rules) {
49+
if (rule.pattern.test(normalized)) {
50+
matches.push({
51+
text: rule.text,
52+
category: rule.category,
53+
severity: rule.severity
54+
});
4155
}
56+
}
57+
58+
if (matches.length === 0) {
59+
return { blocked: false, matches: [] };
60+
}
61+
62+
const severityOrder = { low: 1, medium: 2, high: 3 };
63+
const highest = matches.reduce((a, b) =>
64+
(severityOrder[b.severity] || 1) > (severityOrder[a.severity] || 1)
65+
? b
66+
: a
67+
);
4268

43-
const data = await response.json();
44-
45-
return {
46-
blocked: data.blocked,
47-
reason: data.reason,
48-
confidence: data.confidence,
49-
categories: data.categories
50-
};
51-
} catch (err) {
52-
console.error("CensorCore: API error", err);
53-
return { blocked: false, error: true };
69+
return {
70+
blocked: true,
71+
severity: highest.severity,
72+
category: highest.category,
73+
matches
74+
};
75+
},
76+
77+
extend(customRules) {
78+
if (!Array.isArray(customRules)) return;
79+
80+
for (const r of customRules) {
81+
if (!r || !r.text) continue;
82+
83+
const text = String(r.text).toLowerCase();
84+
const category = r.category || "custom";
85+
const severity = r.severity || CATEGORY_SEVERITY[category] || "low";
86+
87+
rules.push({
88+
text,
89+
category,
90+
severity,
91+
pattern: buildPattern(text)
92+
});
5493
}
5594
},
5695

@@ -77,13 +116,55 @@
77116

78117
window.censor = Object.freeze(censor);
79118

80-
// Immediately ready — no wordlist to load
81-
try {
82-
ready = true;
83-
readyCallbacks.forEach(cb => cb());
84-
} catch (err) {
85-
loadFailed = true;
86-
errorCallbacks.forEach(cb => cb(err));
119+
// Load JSON wordlist
120+
fetch("https://cdn.jsdelivr.net/gh/DerrickRichard/CensorCore-Library@main/wordlist.json")
121+
.then(res => res.json())
122+
.then(data => {
123+
const newRules = [];
124+
125+
for (const [category, list] of Object.entries(data || {})) {
126+
const severity =
127+
CATEGORY_SEVERITY[category] || CATEGORY_SEVERITY.default;
128+
129+
if (!Array.isArray(list)) continue;
130+
131+
for (const entry of list) {
132+
if (!entry) continue;
133+
134+
const text = String(entry).toLowerCase();
135+
136+
newRules.push({
137+
text,
138+
category,
139+
severity,
140+
pattern: buildPattern(text)
141+
});
142+
}
143+
}
144+
145+
rules = newRules;
146+
ready = true;
147+
readyCallbacks.forEach(cb => cb());
148+
})
149+
.catch(err => {
150+
loadFailed = true;
151+
errorCallbacks.forEach(cb => cb(err));
152+
console.error("CensorCore: Could not load wordlist.json", err);
153+
});
154+
155+
function normalizeText(text) {
156+
return String(text)
157+
.trim()
158+
.toLowerCase()
159+
.normalize("NFKC");
160+
}
161+
162+
function buildPattern(text) {
163+
const escaped = escapeRegex(text);
164+
return new RegExp("\\b" + escaped + "\\b", "i");
87165
}
88-
})();
89166

167+
function escapeRegex(str) {
168+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
169+
}
170+
})();

0 commit comments

Comments
 (0)