Detects and highlights AI-generated phrases in Google Docs
Size
10.7 KB
Version
1.0.1
Created
Dec 16, 2025
Updated
about 1 month ago
1// ==UserScript==
2// @name AI Content Detector for Google Docs
3// @description Detects and highlights AI-generated phrases in Google Docs
4// @version 1.0.1
5// @match https://*.docs.google.com/*
6// @icon https://ssl.gstatic.com/docs/documents/images/kix-favicon-2023q4.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 // Common AI-generated phrases to detect
12 const AI_PHRASES = [
13 // Common AI transitions and connectors
14 'in conclusion',
15 'in summary',
16 'to summarize',
17 'it is important to note',
18 'it is worth noting',
19 'it should be noted',
20 'it is essential to',
21 'it is crucial to',
22 'it is vital to',
23 'furthermore',
24 'moreover',
25 'additionally',
26 'consequently',
27 'therefore',
28 'thus',
29 'hence',
30 'accordingly',
31 'as a result',
32 'in light of',
33 'with that being said',
34 'that being said',
35 'having said that',
36
37 // AI-style qualifiers
38 'delve into',
39 'delve deeper',
40 'dive into',
41 'dive deeper',
42 'explore the nuances',
43 'multifaceted',
44 'intricate',
45 'complex interplay',
46 'myriad of',
47 'plethora of',
48 'a testament to',
49 'underscores the importance',
50 'underscores the need',
51 'paramount importance',
52 'of utmost importance',
53
54 // Formal/robotic phrases
55 'in today\'s digital age',
56 'in today\'s world',
57 'in the modern era',
58 'in this day and age',
59 'it goes without saying',
60 'needless to say',
61 'suffice it to say',
62 'rest assured',
63 'bear in mind',
64 'take into consideration',
65 'take into account',
66
67 // AI hedging language
68 'it can be argued',
69 'one could argue',
70 'some may argue',
71 'arguably',
72 'to a certain extent',
73 'to some degree',
74 'in many ways',
75 'in various ways',
76 'in numerous ways',
77
78 // Overly formal conclusions
79 'in the final analysis',
80 'all things considered',
81 'when all is said and done',
82 'at the end of the day',
83 'in the grand scheme',
84 'on a broader scale',
85
86 // AI enthusiasm markers
87 'exciting opportunity',
88 'exciting possibilities',
89 'game-changing',
90 'revolutionary approach',
91 'cutting-edge',
92 'state-of-the-art',
93 'ever-evolving',
94 'ever-changing',
95 'rapidly evolving',
96 'constantly evolving',
97
98 // Redundant AI phrases
99 'absolutely essential',
100 'completely unique',
101 'very unique',
102 'quite literally',
103 'literally speaking',
104 'basically',
105 'essentially',
106 'fundamentally',
107
108 // AI list starters
109 'first and foremost',
110 'last but not least',
111 'it\'s important to remember',
112 'keep in mind that',
113 'don\'t forget that',
114
115 // Overly descriptive
116 'comprehensive guide',
117 'ultimate guide',
118 'complete guide',
119 'in-depth analysis',
120 'thorough examination',
121 'detailed exploration',
122
123 // AI empathy attempts
124 'it\'s understandable',
125 'understandably so',
126 'it makes sense',
127 'rightfully so',
128
129 // Generic AI conclusions
130 'the bottom line',
131 'the key takeaway',
132 'the main point',
133 'what this means',
134 'what this tells us'
135 ];
136
137 // Debounce function to avoid excessive processing
138 function debounce(func, wait) {
139 let timeout;
140 return function executedFunction(...args) {
141 const later = () => {
142 clearTimeout(timeout);
143 func(...args);
144 };
145 clearTimeout(timeout);
146 timeout = setTimeout(later, wait);
147 };
148 }
149
150 // Function to detect AI phrases in text
151 function detectAIPhrases(text) {
152 const detectedPhrases = [];
153 const lowerText = text.toLowerCase();
154
155 AI_PHRASES.forEach(phrase => {
156 const regex = new RegExp('\\b' + phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'gi');
157 let match;
158 while ((match = regex.exec(text)) !== null) {
159 detectedPhrases.push({
160 phrase: match[0],
161 index: match.index,
162 length: match[0].length
163 });
164 }
165 });
166
167 return detectedPhrases;
168 }
169
170 // Function to highlight AI phrases in Google Docs
171 function highlightAIPhrases() {
172 console.log('AI Content Detector: Scanning document for AI phrases...');
173
174 // Get the document canvas
175 const canvas = document.querySelector('.kix-canvas-tile-content');
176 if (!canvas) {
177 console.log('AI Content Detector: Document canvas not found yet');
178 return;
179 }
180
181 // Get all text spans in the document
182 const textSpans = document.querySelectorAll('.kix-wordhtmlgenerator-word-node, span[role="presentation"]');
183
184 if (textSpans.length === 0) {
185 console.log('AI Content Detector: No text spans found in document');
186 return;
187 }
188
189 let detectedCount = 0;
190
191 // Process each text span
192 textSpans.forEach(span => {
193 const text = span.textContent;
194 if (!text || text.trim().length === 0) return;
195
196 const detectedPhrases = detectAIPhrases(text);
197
198 if (detectedPhrases.length > 0) {
199 detectedCount += detectedPhrases.length;
200
201 // Add visual indicator (background highlight)
202 if (!span.classList.contains('ai-phrase-detected')) {
203 span.classList.add('ai-phrase-detected');
204 span.style.backgroundColor = '#ffeb3b';
205 span.style.borderBottom = '2px solid #ff9800';
206 span.title = `AI phrase detected: "${detectedPhrases[0].phrase}"`;
207
208 console.log(`AI Content Detector: Found "${detectedPhrases[0].phrase}" in text`);
209 }
210 }
211 });
212
213 if (detectedCount > 0) {
214 console.log(`AI Content Detector: Found ${detectedCount} AI phrases in document`);
215 showDetectionSummary(detectedCount);
216 } else {
217 console.log('AI Content Detector: No AI phrases detected');
218 }
219 }
220
221 // Show detection summary banner
222 function showDetectionSummary(count) {
223 // Remove existing banner if present
224 const existingBanner = document.getElementById('ai-detector-banner');
225 if (existingBanner) {
226 existingBanner.remove();
227 }
228
229 // Create banner
230 const banner = document.createElement('div');
231 banner.id = 'ai-detector-banner';
232 banner.style.cssText = `
233 position: fixed;
234 top: 60px;
235 right: 20px;
236 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
237 color: white;
238 padding: 15px 20px;
239 border-radius: 8px;
240 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
241 z-index: 10000;
242 font-family: 'Google Sans', Arial, sans-serif;
243 font-size: 14px;
244 font-weight: 500;
245 display: flex;
246 align-items: center;
247 gap: 10px;
248 animation: slideIn 0.3s ease-out;
249 `;
250
251 banner.innerHTML = `
252 <span style="font-size: 20px;">⚠️</span>
253 <span><strong>${count}</strong> AI phrase${count > 1 ? 's' : ''} detected</span>
254 <button id="ai-detector-close" style="
255 background: rgba(255,255,255,0.2);
256 border: none;
257 color: white;
258 padding: 5px 10px;
259 border-radius: 4px;
260 cursor: pointer;
261 margin-left: 10px;
262 font-size: 12px;
263 ">✕</button>
264 `;
265
266 document.body.appendChild(banner);
267
268 // Add close button functionality
269 document.getElementById('ai-detector-close').addEventListener('click', () => {
270 banner.remove();
271 });
272
273 // Auto-hide after 10 seconds
274 setTimeout(() => {
275 if (banner.parentElement) {
276 banner.style.animation = 'slideOut 0.3s ease-out';
277 setTimeout(() => banner.remove(), 300);
278 }
279 }, 10000);
280 }
281
282 // Add CSS animations
283 TM_addStyle(`
284 @keyframes slideIn {
285 from {
286 transform: translateX(400px);
287 opacity: 0;
288 }
289 to {
290 transform: translateX(0);
291 opacity: 1;
292 }
293 }
294
295 @keyframes slideOut {
296 from {
297 transform: translateX(0);
298 opacity: 1;
299 }
300 to {
301 transform: translateX(400px);
302 opacity: 0;
303 }
304 }
305
306 .ai-phrase-detected {
307 position: relative;
308 transition: all 0.2s ease;
309 }
310
311 .ai-phrase-detected:hover {
312 background-color: #ffd54f !important;
313 }
314 `);
315
316 // Debounced version of highlight function
317 const debouncedHighlight = debounce(highlightAIPhrases, 1000);
318
319 // Initialize the detector
320 function init() {
321 console.log('AI Content Detector: Extension loaded');
322
323 // Wait for document to be ready
324 TM_runBody(() => {
325 console.log('AI Content Detector: Document body ready');
326
327 // Initial scan after a delay to ensure content is loaded
328 setTimeout(highlightAIPhrases, 3000);
329
330 // Watch for changes in the document
331 const observer = new MutationObserver(debouncedHighlight);
332
333 // Observe the document editor
334 const editorContainer = document.querySelector('#docs-editor');
335 if (editorContainer) {
336 observer.observe(editorContainer, {
337 childList: true,
338 subtree: true,
339 characterData: true
340 });
341 console.log('AI Content Detector: Monitoring document for changes');
342 } else {
343 console.log('AI Content Detector: Editor container not found, will retry');
344 // Retry after a delay
345 setTimeout(init, 2000);
346 }
347 });
348 }
349
350 // Start the extension
351 init();
352})();