Greasy Fork is available in English.
Detect and fix your/you're, there/their/they're, and contraction mistakes. With Web Discord Support
// ==UserScript==
// @name Grammar Fixer
// @namespace https://forums.somethingawful.com
// @version 1.1.0
// @description Detect and fix your/you're, there/their/they're, and contraction mistakes. With Web Discord Support
// @match *://*/*
// @grant none
// @author Chunjee
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Configuration
// Set to true to auto-correct, false to just highlight
const AUTO_FIX = true;
const WORD_BOUNDARY_PATTERN = (word) => `(^|[\\s.,!?;:'"()\\[\\]{}])(${word})(?=[\\s.,!?;:'"()\\[\\]{}]|$)`;
const patterns = [
// there -> their patterns (possessive contexts)
{ wrong: /\bthere\s+(money|car|house|dog|cat|problem|fault|idea|plan|way|own|job|life|family|friend|work|business|account)\b/gi,
correct: "their $1",
description: "there → their" },
// their -> they're patterns
{ wrong: /\btheir\s+(going|not|sure|probably|definitely|investing|working|coming|leaving|staying|running|walking|talking|thinking|planning|doing|making|taking|giving|getting|looking|feeling|being|having|saying|trying)\b/gi,
correct: "they're $1",
description: "their → they're" },
// there -> they're patterns
{ wrong: /\bthere\s+(going|not|sure|probably|definitely|always|never)\b/gi,
correct: "they're $1",
description: "there → they're" },
// your -> you're patterns
{ wrong: /\byour\s+(trying|going|not|really|probably|definitely|always|never)\b/gi,
correct: "you're $1",
description: "your → you're" },
// Common contractions missing apostrophes
{ wrong: /\bcant\b/gi,
correct: "can't",
description: "cant → can't" },
{ wrong: /\bdont\b/gi,
correct: "don't",
description: "dont → don't" },
{ wrong: /\bwont\b/gi,
correct: "won't",
description: "wont → won't" },
{ wrong: /\bdidnt\b/gi,
correct: "didn't",
description: "didnt → didn't" },
{ wrong: /\bcouldnt\b/gi,
correct: "couldn't",
description: "couldnt → couldn't" },
{ wrong: /\bwouldnt\b/gi,
correct: "wouldn't",
description: "wouldnt → wouldn't" },
{ wrong: /\bshouldnt\b/gi,
correct: "shouldn't",
description: "shouldnt → shouldn't" },
{ wrong: /\bisnt\b/gi,
correct: "isn't",
description: "isnt → isn't" },
{ wrong: /\barent\b/gi,
correct: "aren't",
description: "arent → aren't" },
{ wrong: /\bwasnt\b/gi,
correct: "wasn't",
description: "wasnt → wasn't" },
{ wrong: /\bwerent\b/gi,
correct: "weren't",
description: "werent → weren't" },
{ wrong: /\bhasnt\b/gi,
correct: "hasn't",
description: "hasnt → hasn't" },
{ wrong: /\bhavent\b/gi,
correct: "haven't",
description: "havent → haven't" },
{ wrong: /\bhadnt\b/gi,
correct: "hadn't",
description: "hadnt → hadn't" },
{ wrong: /\bthats\b/gi,
correct: "that's",
description: "thats → that's" },
{ wrong: /\bwhats\b/gi,
correct: "what's",
description: "whats → what's" },
{ wrong: /\blets\b/gi,
correct: "let's",
description: "lets → let's" },
{ wrong: /\bits\s+(not|been|going|time)\b/gi,
correct: "it's $1",
description: "its → it's" },
{ wrong: /\bim\b/gi,
correct: "I'm",
description: "im → I'm" },
{ wrong: /\bive\b/gi,
correct: "I've",
description: "ive → I've" },
{ wrong: /\bwhos\b/gi,
correct: "who's",
description: "whos → who's" },
{ wrong: /\bhes\b/gi,
correct: "he's",
description: "hes → he's" },
{ wrong: /\bshes\b/gi,
correct: "she's",
description: "shes → she's" },
{ wrong: /\btheyll\b/gi,
correct: "they'll",
description: "theyll → they'll" },
{ wrong: /\byoull\b/gi,
correct: "you'll",
description: "youll → you'll" }
];
// Check if we're on Discord
const isDiscord = window.location.hostname.includes('discord.com');
function processTextNode(node) {
let text = node.textContent;
let modified = false;
patterns.forEach(pattern => {
if (pattern.wrong.test(text)) {
if (AUTO_FIX) {
text = text.replace(pattern.wrong, pattern.correct);
modified = true;
} else {
// Highlight mode
const parent = node.parentNode;
const wrapper = document.createElement('span');
wrapper.innerHTML = text.replace(pattern.wrong,
'<mark style="background-color: yellow; cursor: help;" title="' +
pattern.description + '">$&</mark>');
parent.replaceChild(wrapper, node);
modified = true;
}
}
});
if (AUTO_FIX && modified) {
node.textContent = text;
}
}
function walkNodes(node) {
if (node.nodeType === Node.TEXT_NODE) {
processTextNode(node);
} else if (node.nodeType === Node.ELEMENT_NODE &&
!['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'CODE', 'PRE'].includes(node.tagName)) {
Array.from(node.childNodes).forEach(walkNodes);
}
}
// Discord-specific processing
function replaceInElement(el) {
el.childNodes.forEach(node => {
if (node.nodeType === Node.TEXT_NODE) {
processTextNode(node);
} else if (node.nodeType === Node.ELEMENT_NODE && !['CODE', 'PRE'].includes(node.tagName)) {
replaceInElement(node);
}
});
}
if (isDiscord) {
// Discord-specific observer
const observer = new MutationObserver(() => {
document.querySelectorAll('[id^=message-content-]').forEach(el => {
if (el.dataset.grammarFixed) return;
replaceInElement(el);
el.dataset.grammarFixed = 'true';
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Process existing Discord messages
document.querySelectorAll('[id^=message-content-]').forEach(el => {
replaceInElement(el);
el.dataset.grammarFixed = 'true';
});
} else {
// General site processing
walkNodes(document.body);
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
walkNodes(node);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
})();