27
stars
463
commits
TypeScript
primary language
Feb 14, 2026
updated
A pattern detector for manipulative outrage framing in media. This is NOT a fact-checker and NOT a political bias judge - it's a tool that flags common outrage-bait patterns to help users think critically about content.
# Install dependencies
npm install
# Run development server
npm run dev
# Build for production
npm run build
# Start production server
npm start
Open http://localhost:3000 to use the app.
Or deploy via CLI:
npm i -g vercel
vercel
BaitCheck uses a rule-based scoring system that analyzes text for five categories of outrage-bait patterns:
| Category | Weight | Description |
|---|---|---|
| Loaded Language | 25% | Emotional, inflammatory words (e.g., "disgusting", "evil", "scum") |
| Absolutist | 15% | Certainty/black-and-white language (e.g., "always", "never", "everyone knows") |
| Threat/Panic | 25% | Fear-mongering framing (e.g., "they're coming for", "under attack", "collapse") |
| Us-vs-Them | 15% | Divisive in-group/out-group language (e.g., "those people", "elites", "real Americans") |
| Engagement Bait | 20% | Clickbait/viral patterns (e.g., "you won't believe", "shocking", "must see") |
src/
├── app/
│ ├── api/
│ │ └── analyze/
│ │ └── route.ts # POST /api/analyze endpoint
│ ├── layout.tsx # App layout
│ ├── page.tsx # Main UI
│ └── globals.css # Styles
└── lib/
├── extract.ts # URL fetching & text extraction
└── score.ts # Scoring pipeline & dictionaries
Analyzes a URL for outrage-bait patterns.
Request:
{
"url": "https://example.com/article"
}
Response:
{
"success": true,
"score": 45,
"label": "Medium",
"reasons": [
"Heavy loaded language and emotional appeals (\"outrageous\", \"disgusting\")",
"Strong us-vs-them divisive framing (\"they\", \"elites\")"
],
"highlights": [
{ "start": 123, "end": 133, "category": "loadedLanguage", "text": "outrageous" }
],
"signalBreakdown": {
"loadedLanguage": 65,
"absolutist": 20,
"threatPanic": 30,
"usVsThem": 45,
"engagementBait": 15
},
"title": "Article Title",
"sourceDomain": "example.com",
"textPreview": "First 500 characters of extracted text..."
}
Edit src/lib/score.ts and add to the SIGNAL_DICTIONARIES object:
const SIGNAL_DICTIONARIES: Record<SignalCategory, string[]> = {
loadedLanguage: [
// Add new phrases here
"new phrase",
"another phrase",
...existingPhrases
],
// ... other categories
};
Edit the CATEGORY_WEIGHTS object in src/lib/score.ts:
const CATEGORY_WEIGHTS: Record<SignalCategory, number> = {
loadedLanguage: 25, // Adjust these values
absolutist: 15, // Must sum to 100
threatPanic: 25,
usVsThem: 15,
engagementBait: 20,
};
Modify the multipliers in the signalBreakdown calculation:
const signalBreakdown: SignalBreakdown = {
loadedLanguage: Math.min(100, normalizedCounts.loadedLanguage * 10), // Adjust multiplier
// ...
};
MIT
463 commits
TypeScript
91.1%
Python
7.9%
27
stars
463
commits
TypeScript
primary language
Feb 14, 2026
updated
A pattern detector for manipulative outrage framing in media. This is NOT a fact-checker and NOT a political bias judge - it's a tool that flags common outrage-bait patterns to help users think critically about content.
# Install dependencies
npm install
# Run development server
npm run dev
# Build for production
npm run build
# Start production server
npm start
Open http://localhost:3000 to use the app.
Or deploy via CLI:
npm i -g vercel
vercel
BaitCheck uses a rule-based scoring system that analyzes text for five categories of outrage-bait patterns:
| Category | Weight | Description |
|---|---|---|
| Loaded Language | 25% | Emotional, inflammatory words (e.g., "disgusting", "evil", "scum") |
| Absolutist | 15% | Certainty/black-and-white language (e.g., "always", "never", "everyone knows") |
| Threat/Panic | 25% | Fear-mongering framing (e.g., "they're coming for", "under attack", "collapse") |
| Us-vs-Them | 15% | Divisive in-group/out-group language (e.g., "those people", "elites", "real Americans") |
| Engagement Bait | 20% | Clickbait/viral patterns (e.g., "you won't believe", "shocking", "must see") |
src/
├── app/
│ ├── api/
│ │ └── analyze/
│ │ └── route.ts # POST /api/analyze endpoint
│ ├── layout.tsx # App layout
│ ├── page.tsx # Main UI
│ └── globals.css # Styles
└── lib/
├── extract.ts # URL fetching & text extraction
└── score.ts # Scoring pipeline & dictionaries
Analyzes a URL for outrage-bait patterns.
Request:
{
"url": "https://example.com/article"
}
Response:
{
"success": true,
"score": 45,
"label": "Medium",
"reasons": [
"Heavy loaded language and emotional appeals (\"outrageous\", \"disgusting\")",
"Strong us-vs-them divisive framing (\"they\", \"elites\")"
],
"highlights": [
{ "start": 123, "end": 133, "category": "loadedLanguage", "text": "outrageous" }
],
"signalBreakdown": {
"loadedLanguage": 65,
"absolutist": 20,
"threatPanic": 30,
"usVsThem": 45,
"engagementBait": 15
},
"title": "Article Title",
"sourceDomain": "example.com",
"textPreview": "First 500 characters of extracted text..."
}
Edit src/lib/score.ts and add to the SIGNAL_DICTIONARIES object:
const SIGNAL_DICTIONARIES: Record<SignalCategory, string[]> = {
loadedLanguage: [
// Add new phrases here
"new phrase",
"another phrase",
...existingPhrases
],
// ... other categories
};
Edit the CATEGORY_WEIGHTS object in src/lib/score.ts:
const CATEGORY_WEIGHTS: Record<SignalCategory, number> = {
loadedLanguage: 25, // Adjust these values
absolutist: 15, // Must sum to 100
threatPanic: 25,
usVsThem: 15,
engagementBait: 20,
};
Modify the multipliers in the signalBreakdown calculation:
const signalBreakdown: SignalBreakdown = {
loadedLanguage: Math.min(100, normalizedCounts.loadedLanguage * 10), // Adjust multiplier
// ...
};
MIT
463 commits
TypeScript
91.1%
Python
7.9%