Size
12.8 KB
Version
1.0.1
Created
Feb 28, 2026
Updated
about 2 months ago
1// ==UserScript==
2// @name Teromi Form Auto-Filler
3// @description Automatically fills Google Forms with saved information
4// @version 1.0.1
5// @match https://*.docs.google.com/*
6// @icon https://ssl.gstatic.com/docs/spreadsheets/forms/favicon_qp2.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Teromi Form Auto-Filler initialized');
12
13 // Debounce function to prevent excessive calls
14 function debounce(func, wait) {
15 let timeout;
16 return function executedFunction(...args) {
17 const later = () => {
18 clearTimeout(timeout);
19 func(...args);
20 };
21 clearTimeout(timeout);
22 timeout = setTimeout(later, wait);
23 };
24 }
25
26 // Create the auto-fill button UI
27 function createAutoFillButton() {
28 const button = document.createElement('div');
29 button.id = 'teromi-autofill-btn';
30 button.innerHTML = `
31 <div style="display: flex; align-items: center; gap: 8px;">
32 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
33 <path d="M9 11l3 3L22 4"></path>
34 <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"></path>
35 </svg>
36 <span>Auto-Fill Form</span>
37 </div>
38 `;
39 button.style.cssText = `
40 position: fixed;
41 top: 20px;
42 right: 20px;
43 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
44 color: white;
45 padding: 12px 24px;
46 border-radius: 8px;
47 cursor: pointer;
48 font-family: 'Google Sans', Roboto, Arial, sans-serif;
49 font-size: 14px;
50 font-weight: 500;
51 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
52 z-index: 10000;
53 transition: all 0.3s ease;
54 user-select: none;
55 `;
56
57 button.addEventListener('mouseenter', () => {
58 button.style.transform = 'translateY(-2px)';
59 button.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.5)';
60 });
61
62 button.addEventListener('mouseleave', () => {
63 button.style.transform = 'translateY(0)';
64 button.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.4)';
65 });
66
67 button.addEventListener('click', handleAutoFill);
68 document.body.appendChild(button);
69 console.log('Auto-fill button created');
70 }
71
72 // Create settings button
73 function createSettingsButton() {
74 const button = document.createElement('div');
75 button.id = 'teromi-settings-btn';
76 button.innerHTML = `
77 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
78 <circle cx="12" cy="12" r="3"></circle>
79 <path d="M12 1v6m0 6v6m-6-6h6m6 0h-6"></path>
80 </svg>
81 `;
82 button.style.cssText = `
83 position: fixed;
84 top: 20px;
85 right: 180px;
86 background: white;
87 color: #5f6368;
88 padding: 12px;
89 border-radius: 8px;
90 cursor: pointer;
91 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
92 z-index: 10000;
93 transition: all 0.3s ease;
94 border: 1px solid #dadce0;
95 `;
96
97 button.addEventListener('mouseenter', () => {
98 button.style.background = '#f8f9fa';
99 button.style.transform = 'translateY(-2px)';
100 });
101
102 button.addEventListener('mouseleave', () => {
103 button.style.background = 'white';
104 button.style.transform = 'translateY(0)';
105 });
106
107 button.addEventListener('click', showSettingsPanel);
108 document.body.appendChild(button);
109 console.log('Settings button created');
110 }
111
112 // Show settings panel
113 async function showSettingsPanel() {
114 const existingPanel = document.getElementById('teromi-settings-panel');
115 if (existingPanel) {
116 existingPanel.remove();
117 return;
118 }
119
120 const savedData = await getSavedFormData();
121
122 const panel = document.createElement('div');
123 panel.id = 'teromi-settings-panel';
124 panel.innerHTML = `
125 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
126 <h3 style="margin: 0; color: #202124; font-size: 18px; font-weight: 500;">Saved Information</h3>
127 <div id="close-settings" style="cursor: pointer; color: #5f6368; font-size: 24px; line-height: 1;">×</div>
128 </div>
129 <div style="margin-bottom: 16px;">
130 <label style="display: block; margin-bottom: 6px; color: #5f6368; font-size: 13px; font-weight: 500;">Full Name</label>
131 <input type="text" id="saved-fullname" value="${savedData.fullname || ''}"
132 style="width: 100%; padding: 10px; border: 1px solid #dadce0; border-radius: 4px; font-size: 14px; box-sizing: border-box;">
133 </div>
134 <div style="margin-bottom: 16px;">
135 <label style="display: block; margin-bottom: 6px; color: #5f6368; font-size: 13px; font-weight: 500;">Email</label>
136 <input type="email" id="saved-email" value="${savedData.email || ''}"
137 style="width: 100%; padding: 10px; border: 1px solid #dadce0; border-radius: 4px; font-size: 14px; box-sizing: border-box;">
138 </div>
139 <div style="margin-bottom: 20px;">
140 <label style="display: block; margin-bottom: 6px; color: #5f6368; font-size: 13px; font-weight: 500;">Phone Number</label>
141 <input type="tel" id="saved-phone" value="${savedData.phone || ''}"
142 style="width: 100%; padding: 10px; border: 1px solid #dadce0; border-radius: 4px; font-size: 14px; box-sizing: border-box;">
143 </div>
144 <button id="save-settings-btn" style="width: 100%; padding: 12px; background: #1a73e8; color: white; border: none; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; transition: background 0.2s;">
145 Save Information
146 </button>
147 `;
148 panel.style.cssText = `
149 position: fixed;
150 top: 80px;
151 right: 20px;
152 background: white;
153 padding: 24px;
154 border-radius: 8px;
155 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
156 z-index: 10001;
157 width: 320px;
158 font-family: 'Google Sans', Roboto, Arial, sans-serif;
159 `;
160
161 document.body.appendChild(panel);
162
163 // Event listeners
164 document.getElementById('close-settings').addEventListener('click', () => panel.remove());
165 document.getElementById('save-settings-btn').addEventListener('click', async () => {
166 const fullname = document.getElementById('saved-fullname').value;
167 const email = document.getElementById('saved-email').value;
168 const phone = document.getElementById('saved-phone').value;
169
170 await GM.setValue('teromi_fullname', fullname);
171 await GM.setValue('teromi_email', email);
172 await GM.setValue('teromi_phone', phone);
173
174 showNotification('Information saved successfully!', 'success');
175 panel.remove();
176 });
177
178 // Hover effect for save button
179 const saveBtn = document.getElementById('save-settings-btn');
180 saveBtn.addEventListener('mouseenter', () => {
181 saveBtn.style.background = '#1765cc';
182 });
183 saveBtn.addEventListener('mouseleave', () => {
184 saveBtn.style.background = '#1a73e8';
185 });
186 }
187
188 // Get saved form data
189 async function getSavedFormData() {
190 const fullname = await GM.getValue('teromi_fullname', '');
191 const email = await GM.getValue('teromi_email', '');
192 const phone = await GM.getValue('teromi_phone', '');
193 return { fullname, email, phone };
194 }
195
196 // Handle auto-fill
197 async function handleAutoFill() {
198 console.log('Auto-fill triggered');
199 const savedData = await getSavedFormData();
200
201 if (!savedData.fullname && !savedData.email && !savedData.phone) {
202 showNotification('No saved information found. Please configure your details first.', 'warning');
203 showSettingsPanel();
204 return;
205 }
206
207 // Find and fill form fields
208 const inputs = document.querySelectorAll('input.whsOnd.zHQkBf[type="text"]');
209 console.log(`Found ${inputs.length} input fields`);
210
211 inputs.forEach((input, index) => {
212 const labelId = input.getAttribute('aria-labelledby');
213 if (!labelId) return;
214
215 const labelElement = document.getElementById(labelId.split(' ')[0]);
216 if (!labelElement) return;
217
218 const labelText = labelElement.textContent.trim().toLowerCase();
219 console.log(`Field ${index}: ${labelText}`);
220
221 if (labelText.includes('name') || labelText.includes('имя')) {
222 input.value = savedData.fullname;
223 input.dispatchEvent(new Event('input', { bubbles: true }));
224 input.dispatchEvent(new Event('change', { bubbles: true }));
225 console.log('Filled name field');
226 } else if (labelText.includes('email') || labelText.includes('почта')) {
227 input.value = savedData.email;
228 input.dispatchEvent(new Event('input', { bubbles: true }));
229 input.dispatchEvent(new Event('change', { bubbles: true }));
230 console.log('Filled email field');
231 } else if (labelText.includes('phone') || labelText.includes('телефон')) {
232 input.value = savedData.phone;
233 input.dispatchEvent(new Event('input', { bubbles: true }));
234 input.dispatchEvent(new Event('change', { bubbles: true }));
235 console.log('Filled phone field');
236 }
237 });
238
239 showNotification('Form filled successfully!', 'success');
240 }
241
242 // Show notification
243 function showNotification(message, type = 'info') {
244 const notification = document.createElement('div');
245 notification.textContent = message;
246
247 const colors = {
248 success: '#0f9d58',
249 warning: '#f9ab00',
250 error: '#d93025',
251 info: '#1a73e8'
252 };
253
254 notification.style.cssText = `
255 position: fixed;
256 top: 80px;
257 right: 20px;
258 background: ${colors[type]};
259 color: white;
260 padding: 14px 20px;
261 border-radius: 8px;
262 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
263 z-index: 10002;
264 font-family: 'Google Sans', Roboto, Arial, sans-serif;
265 font-size: 14px;
266 font-weight: 500;
267 animation: slideIn 0.3s ease;
268 `;
269
270 document.body.appendChild(notification);
271
272 setTimeout(() => {
273 notification.style.animation = 'slideOut 0.3s ease';
274 setTimeout(() => notification.remove(), 300);
275 }, 3000);
276 }
277
278 // Add CSS animations
279 const style = document.createElement('style');
280 style.textContent = `
281 @keyframes slideIn {
282 from {
283 transform: translateX(400px);
284 opacity: 0;
285 }
286 to {
287 transform: translateX(0);
288 opacity: 1;
289 }
290 }
291 @keyframes slideOut {
292 from {
293 transform: translateX(0);
294 opacity: 1;
295 }
296 to {
297 transform: translateX(400px);
298 opacity: 0;
299 }
300 }
301 `;
302 document.head.appendChild(style);
303
304 // Initialize when form is detected
305 function init() {
306 // Check if we're on a Google Form
307 const isGoogleForm = window.location.href.includes('docs.google.com/forms');
308 if (!isGoogleForm) {
309 console.log('Not a Google Form, extension inactive');
310 return;
311 }
312
313 console.log('Google Form detected, initializing extension');
314
315 // Wait for form to be fully loaded
316 const checkFormLoaded = setInterval(() => {
317 const formInputs = document.querySelectorAll('input.whsOnd.zHQkBf[type="text"]');
318 if (formInputs.length > 0) {
319 clearInterval(checkFormLoaded);
320 console.log('Form loaded, creating UI');
321 createAutoFillButton();
322 createSettingsButton();
323 }
324 }, 500);
325
326 // Clear interval after 10 seconds to prevent infinite checking
327 setTimeout(() => clearInterval(checkFormLoaded), 10000);
328 }
329
330 // Start the extension
331 if (document.readyState === 'loading') {
332 document.addEventListener('DOMContentLoaded', init);
333 } else {
334 init();
335 }
336})();