Teromi Form Auto-Filler

Automatically fills Google Forms with saved information

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;">&times;</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})();