AI Grammar Checker (Grammarly Alternative) for Violentmonkey

Underlines grammar errors and shows corrections on click! (Like Grammarly Premium!)

  1. // ==UserScript==
  2. // @name AI Grammar Checker (Grammarly Alternative) for Violentmonkey
  3. // @namespace http://violentmonkey.net/
  4. // @version 1.4
  5. // @description Underlines grammar errors and shows corrections on click! (Like Grammarly Premium!)
  6. // @author You
  7. // @match *://*/*
  8. // @grant GM_xmlhttpRequest
  9. // @connect api.languagetool.org
  10. // ==/UserScript==
  11.  
  12. var GrammarChecker = {};
  13.  
  14. GrammarChecker.API_URL = "https://api.languagetool.org/v2/check";
  15.  
  16. GrammarChecker.init = function() {
  17. console.log("🚀 Grammar Checker Loaded for Violentmonkey!");
  18.  
  19. // Observe text inputs and editable divs
  20. let observer = new MutationObserver(() => {
  21. document.querySelectorAll("textarea, input[type='text'], [contenteditable='true']").forEach(element => {
  22. if (!element.dataset.grammarChecked) {
  23. element.dataset.grammarChecked = "true";
  24. element.addEventListener("blur", () => GrammarChecker.checkGrammar(element));
  25. }
  26. });
  27. });
  28.  
  29. observer.observe(document.body, { childList: true, subtree: true });
  30. };
  31.  
  32. GrammarChecker.checkGrammar = function(element) {
  33. let text = element.value || element.innerText;
  34. if (!text.trim()) return;
  35.  
  36. console.log("📝 Checking grammar for:", text);
  37.  
  38. GM_xmlhttpRequest({
  39. method: "POST",
  40. url: GrammarChecker.API_URL,
  41. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  42. data: `language=en-US&text=${encodeURIComponent(text)}`,
  43. onload: function(response) {
  44. if (response.status === 200) {
  45. let data = JSON.parse(response.responseText);
  46. GrammarChecker.highlightErrors(element, data);
  47. } else {
  48. console.error("❌ API Error:", response.statusText);
  49. }
  50. },
  51. onerror: function(error) {
  52. console.error("❌ Network Error:", error);
  53. }
  54. });
  55. };
  56.  
  57. GrammarChecker.highlightErrors = function(element, data) {
  58. let errors = data.matches;
  59. if (errors.length === 0) {
  60. console.log("✅ No errors found.");
  61. return;
  62. }
  63.  
  64. if (element.tagName === "TEXTAREA" || element.tagName === "INPUT") {
  65. // Simple text inputs → No rich formatting possible
  66. console.warn("⚠️ Underlining not possible in plain inputs!");
  67. return;
  68. }
  69.  
  70. let html = element.innerHTML;
  71. errors.forEach((error, index) => {
  72. let errorText = error.context.text.substr(error.context.offset, error.context.length);
  73. let replacement = error.replacements.length > 0 ? error.replacements.map(r => r.value).join(", ") : "No suggestions";
  74.  
  75. // Create a unique ID for each error
  76. let errorId = `grammar-error-${index}`;
  77.  
  78. // Replace the error text with an underlined, clickable span
  79. let errorSpan = `<span id="${errorId}" class="grammar-error" data-suggestion="${replacement}">${errorText}</span>`;
  80. html = html.replace(errorText, errorSpan);
  81. });
  82.  
  83. element.innerHTML = html;
  84.  
  85. // Add event listeners for clicking on underlined errors
  86. document.querySelectorAll(".grammar-error").forEach(span => {
  87. span.style.textDecoration = "underline";
  88. span.style.textDecorationColor = "red";
  89. span.style.cursor = "pointer";
  90. span.style.color = "red";
  91.  
  92. span.addEventListener("click", (e) => {
  93. GrammarChecker.showSuggestionBox(e.target, e.target.dataset.suggestion);
  94. });
  95. });
  96. };
  97.  
  98. GrammarChecker.showSuggestionBox = function(target, suggestion) {
  99. // Remove existing tooltips
  100. document.querySelectorAll(".grammar-tooltip").forEach(e => e.remove());
  101.  
  102. let tooltip = document.createElement("div");
  103. tooltip.className = "grammar-tooltip";
  104. tooltip.innerText = `Suggestion: ${suggestion}`;
  105.  
  106. let applyButton = document.createElement("button");
  107. applyButton.innerText = "Apply";
  108. applyButton.style.marginLeft = "10px";
  109. applyButton.style.padding = "5px 10px";
  110. applyButton.style.background = "#27ae60";
  111. applyButton.style.border = "none";
  112. applyButton.style.cursor = "pointer";
  113. applyButton.style.color = "#fff";
  114. applyButton.style.fontWeight = "bold";
  115.  
  116. applyButton.addEventListener("click", () => {
  117. target.innerText = suggestion.split(", ")[0]; // Apply first suggestion
  118. tooltip.remove();
  119. });
  120.  
  121. tooltip.appendChild(applyButton);
  122.  
  123. // Style the tooltip
  124. tooltip.style.position = "absolute";
  125. tooltip.style.background = "#fffa65";
  126. tooltip.style.color = "#333";
  127. tooltip.style.border = "1px solid #f39c12";
  128. tooltip.style.padding = "10px";
  129. tooltip.style.zIndex = "9999";
  130. tooltip.style.fontSize = "14px";
  131. tooltip.style.top = `${target.getBoundingClientRect().bottom + window.scrollY}px`;
  132. tooltip.style.left = `${target.getBoundingClientRect().left}px`;
  133.  
  134. document.body.appendChild(tooltip);
  135.  
  136. // Remove tooltip if clicked outside
  137. document.addEventListener("click", function removeTooltip(event) {
  138. if (!tooltip.contains(event.target) && event.target !== target) {
  139. tooltip.remove();
  140. document.removeEventListener("click", removeTooltip);
  141. }
  142. });
  143. };
  144.  
  145. // Start the script
  146. GrammarChecker.init();
  147.  
  148.  

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址