WhatsApp Message Scheduler

Schedule messages to be sent at a specific time on WhatsApp Web

Size

17.0 KB

Version

1.0.1

Created

Jan 11, 2026

Updated

11 days ago

1// ==UserScript==
2// @name		WhatsApp Message Scheduler
3// @description		Schedule messages to be sent at a specific time on WhatsApp Web
4// @version		1.0.1
5// @match		https://*.web.whatsapp.com/*
6// @icon		https://web.whatsapp.com/favicon/1x/favicon/v4/
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('WhatsApp Message Scheduler initialized');
12
13    // Utility function to debounce
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    // Add custom styles
27    TM_addStyle(`
28        .scheduler-button {
29            background: #00a884;
30            border: none;
31            border-radius: 50%;
32            width: 40px;
33            height: 40px;
34            cursor: pointer;
35            display: flex;
36            align-items: center;
37            justify-content: center;
38            margin-left: 8px;
39            transition: background 0.2s;
40        }
41        
42        .scheduler-button:hover {
43            background: #06cf9c;
44        }
45        
46        .scheduler-button svg {
47            width: 24px;
48            height: 24px;
49            fill: white;
50        }
51        
52        .scheduler-modal {
53            position: fixed;
54            top: 0;
55            left: 0;
56            width: 100%;
57            height: 100%;
58            background: rgba(0, 0, 0, 0.5);
59            display: flex;
60            align-items: center;
61            justify-content: center;
62            z-index: 10000;
63        }
64        
65        .scheduler-modal-content {
66            background: #fff;
67            border-radius: 12px;
68            padding: 24px;
69            width: 90%;
70            max-width: 500px;
71            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
72        }
73        
74        .scheduler-modal-header {
75            font-size: 20px;
76            font-weight: 600;
77            margin-bottom: 20px;
78            color: #111b21;
79        }
80        
81        .scheduler-form-group {
82            margin-bottom: 16px;
83        }
84        
85        .scheduler-form-group label {
86            display: block;
87            margin-bottom: 8px;
88            font-weight: 500;
89            color: #3b4a54;
90            font-size: 14px;
91        }
92        
93        .scheduler-form-group input,
94        .scheduler-form-group textarea {
95            width: 100%;
96            padding: 10px;
97            border: 1px solid #d1d7db;
98            border-radius: 8px;
99            font-size: 14px;
100            font-family: inherit;
101            box-sizing: border-box;
102        }
103        
104        .scheduler-form-group textarea {
105            min-height: 100px;
106            resize: vertical;
107        }
108        
109        .scheduler-form-group input:focus,
110        .scheduler-form-group textarea:focus {
111            outline: none;
112            border-color: #00a884;
113        }
114        
115        .scheduler-buttons {
116            display: flex;
117            gap: 12px;
118            margin-top: 24px;
119        }
120        
121        .scheduler-btn {
122            flex: 1;
123            padding: 12px;
124            border: none;
125            border-radius: 8px;
126            font-size: 14px;
127            font-weight: 600;
128            cursor: pointer;
129            transition: background 0.2s;
130        }
131        
132        .scheduler-btn-primary {
133            background: #00a884;
134            color: white;
135        }
136        
137        .scheduler-btn-primary:hover {
138            background: #06cf9c;
139        }
140        
141        .scheduler-btn-secondary {
142            background: #f0f2f5;
143            color: #3b4a54;
144        }
145        
146        .scheduler-btn-secondary:hover {
147            background: #e4e6eb;
148        }
149        
150        .scheduler-list {
151            margin-top: 20px;
152            max-height: 300px;
153            overflow-y: auto;
154        }
155        
156        .scheduler-item {
157            background: #f0f2f5;
158            padding: 12px;
159            border-radius: 8px;
160            margin-bottom: 8px;
161            display: flex;
162            justify-content: space-between;
163            align-items: center;
164        }
165        
166        .scheduler-item-info {
167            flex: 1;
168        }
169        
170        .scheduler-item-time {
171            font-weight: 600;
172            color: #00a884;
173            margin-bottom: 4px;
174        }
175        
176        .scheduler-item-message {
177            font-size: 13px;
178            color: #667781;
179            white-space: nowrap;
180            overflow: hidden;
181            text-overflow: ellipsis;
182        }
183        
184        .scheduler-item-delete {
185            background: #f15c6d;
186            color: white;
187            border: none;
188            border-radius: 6px;
189            padding: 6px 12px;
190            cursor: pointer;
191            font-size: 12px;
192            margin-left: 12px;
193        }
194        
195        .scheduler-item-delete:hover {
196            background: #d93b4d;
197        }
198        
199        .scheduler-notification {
200            position: fixed;
201            top: 20px;
202            right: 20px;
203            background: #00a884;
204            color: white;
205            padding: 16px 20px;
206            border-radius: 8px;
207            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
208            z-index: 10001;
209            animation: slideIn 0.3s ease;
210        }
211        
212        @keyframes slideIn {
213            from {
214                transform: translateX(400px);
215                opacity: 0;
216            }
217            to {
218                transform: translateX(0);
219                opacity: 1;
220            }
221        }
222    `);
223
224    // Storage keys
225    const STORAGE_KEY = 'whatsapp_scheduled_messages';
226
227    // Get scheduled messages from storage
228    async function getScheduledMessages() {
229        try {
230            const data = await GM.getValue(STORAGE_KEY, '[]');
231            return JSON.parse(data);
232        } catch (error) {
233            console.error('Error getting scheduled messages:', error);
234            return [];
235        }
236    }
237
238    // Save scheduled messages to storage
239    async function saveScheduledMessages(messages) {
240        try {
241            await GM.setValue(STORAGE_KEY, JSON.stringify(messages));
242            console.log('Scheduled messages saved:', messages);
243        } catch (error) {
244            console.error('Error saving scheduled messages:', error);
245        }
246    }
247
248    // Show notification
249    function showNotification(message) {
250        const notification = document.createElement('div');
251        notification.className = 'scheduler-notification';
252        notification.textContent = message;
253        document.body.appendChild(notification);
254        
255        setTimeout(() => {
256            notification.remove();
257        }, 3000);
258    }
259
260    // Send message function
261    async function sendMessage(message) {
262        try {
263            // Find the message input box
264            const messageBox = document.querySelector('[contenteditable="true"][data-tab="10"]');
265            if (!messageBox) {
266                console.error('Message input box not found');
267                return false;
268            }
269
270            // Set the message
271            messageBox.focus();
272            document.execCommand('insertText', false, message);
273
274            // Wait a bit for WhatsApp to process
275            await new Promise(resolve => setTimeout(resolve, 500));
276
277            // Find and click the send button
278            const sendButton = document.querySelector('button[aria-label="Send"]');
279            if (!sendButton) {
280                console.error('Send button not found');
281                return false;
282            }
283
284            sendButton.click();
285            console.log('Message sent successfully:', message);
286            return true;
287        } catch (error) {
288            console.error('Error sending message:', error);
289            return false;
290        }
291    }
292
293    // Check and send scheduled messages
294    async function checkScheduledMessages() {
295        const messages = await getScheduledMessages();
296        const now = new Date().getTime();
297        const remainingMessages = [];
298        
299        for (const msg of messages) {
300            const scheduledTime = new Date(msg.scheduledTime).getTime();
301            
302            if (scheduledTime <= now) {
303                // Time to send this message
304                console.log('Sending scheduled message:', msg);
305                const success = await sendMessage(msg.message);
306                
307                if (success) {
308                    showNotification('Scheduled message sent!');
309                } else {
310                    // If failed, keep it for retry
311                    remainingMessages.push(msg);
312                    showNotification('Failed to send message. Will retry.');
313                }
314            } else {
315                // Not time yet, keep it
316                remainingMessages.push(msg);
317            }
318        }
319        
320        // Save remaining messages
321        if (remainingMessages.length !== messages.length) {
322            await saveScheduledMessages(remainingMessages);
323        }
324    }
325
326    // Create scheduler modal
327    function createSchedulerModal() {
328        const modal = document.createElement('div');
329        modal.className = 'scheduler-modal';
330        modal.innerHTML = `
331            <div class="scheduler-modal-content">
332                <div class="scheduler-modal-header">Schedule Message</div>
333                
334                <div class="scheduler-form-group">
335                    <label>Message</label>
336                    <textarea id="scheduler-message" placeholder="Type your message here..."></textarea>
337                </div>
338                
339                <div class="scheduler-form-group">
340                    <label>Date & Time</label>
341                    <input type="datetime-local" id="scheduler-datetime">
342                </div>
343                
344                <div class="scheduler-buttons">
345                    <button class="scheduler-btn scheduler-btn-secondary" id="scheduler-cancel">Cancel</button>
346                    <button class="scheduler-btn scheduler-btn-primary" id="scheduler-schedule">Schedule</button>
347                </div>
348                
349                <div class="scheduler-list" id="scheduler-list"></div>
350            </div>
351        `;
352        
353        // Set minimum datetime to now
354        const datetimeInput = modal.querySelector('#scheduler-datetime');
355        const now = new Date();
356        now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
357        datetimeInput.min = now.toISOString().slice(0, 16);
358        datetimeInput.value = now.toISOString().slice(0, 16);
359        
360        // Cancel button
361        modal.querySelector('#scheduler-cancel').addEventListener('click', () => {
362            modal.remove();
363        });
364        
365        // Schedule button
366        modal.querySelector('#scheduler-schedule').addEventListener('click', async () => {
367            const message = modal.querySelector('#scheduler-message').value.trim();
368            const datetime = modal.querySelector('#scheduler-datetime').value;
369            
370            if (!message) {
371                alert('Please enter a message');
372                return;
373            }
374            
375            if (!datetime) {
376                alert('Please select a date and time');
377                return;
378            }
379            
380            const scheduledTime = new Date(datetime);
381            if (scheduledTime <= new Date()) {
382                alert('Please select a future date and time');
383                return;
384            }
385            
386            // Add to scheduled messages
387            const messages = await getScheduledMessages();
388            messages.push({
389                id: Date.now(),
390                message: message,
391                scheduledTime: scheduledTime.toISOString(),
392                chatId: getCurrentChatId()
393            });
394            
395            await saveScheduledMessages(messages);
396            showNotification('Message scheduled successfully!');
397            
398            // Refresh the list
399            await updateScheduledList(modal);
400            
401            // Clear form
402            modal.querySelector('#scheduler-message').value = '';
403        });
404        
405        // Close on background click
406        modal.addEventListener('click', (e) => {
407            if (e.target === modal) {
408                modal.remove();
409            }
410        });
411        
412        return modal;
413    }
414
415    // Get current chat ID
416    function getCurrentChatId() {
417        try {
418            const chatHeader = document.querySelector('header[data-tab="2"]');
419            if (chatHeader) {
420                const titleElement = chatHeader.querySelector('span[dir="auto"]');
421                return titleElement ? titleElement.textContent : 'unknown';
422            }
423            return 'unknown';
424        } catch (error) {
425            console.error('Error getting chat ID:', error);
426            return 'unknown';
427        }
428    }
429
430    // Update scheduled messages list
431    async function updateScheduledList(modal) {
432        const listContainer = modal.querySelector('#scheduler-list');
433        const messages = await getScheduledMessages();
434        const currentChatId = getCurrentChatId();
435        
436        // Filter messages for current chat
437        const chatMessages = messages.filter(msg => msg.chatId === currentChatId);
438        
439        if (chatMessages.length === 0) {
440            listContainer.innerHTML = '<div style="text-align: center; color: #667781; padding: 20px;">No scheduled messages</div>';
441            return;
442        }
443        
444        listContainer.innerHTML = chatMessages.map(msg => {
445            const scheduledDate = new Date(msg.scheduledTime);
446            const formattedDate = scheduledDate.toLocaleString();
447            
448            return `
449                <div class="scheduler-item">
450                    <div class="scheduler-item-info">
451                        <div class="scheduler-item-time">${formattedDate}</div>
452                        <div class="scheduler-item-message">${msg.message}</div>
453                    </div>
454                    <button class="scheduler-item-delete" data-id="${msg.id}">Delete</button>
455                </div>
456            `;
457        }).join('');
458        
459        // Add delete handlers
460        listContainer.querySelectorAll('.scheduler-item-delete').forEach(btn => {
461            btn.addEventListener('click', async () => {
462                const id = parseInt(btn.getAttribute('data-id'));
463                const messages = await getScheduledMessages();
464                const filtered = messages.filter(msg => msg.id !== id);
465                await saveScheduledMessages(filtered);
466                await updateScheduledList(modal);
467                showNotification('Scheduled message deleted');
468            });
469        });
470    }
471
472    // Add scheduler button to chat
473    function addSchedulerButton() {
474        // Check if we're in a chat
475        const footer = document.querySelector('footer[class*="copyable-area"]');
476        if (!footer) {
477            console.log('Not in a chat, scheduler button not added');
478            return;
479        }
480
481        // Check if button already exists
482        if (document.querySelector('.scheduler-button')) {
483            return;
484        }
485
486        // Find the attachment button container
487        const attachmentArea = footer.querySelector('div[role="button"][aria-label*="Attach"]')?.parentElement?.parentElement;
488        if (!attachmentArea) {
489            console.log('Attachment area not found');
490            return;
491        }
492
493        // Create scheduler button
494        const schedulerButton = document.createElement('button');
495        schedulerButton.className = 'scheduler-button';
496        schedulerButton.title = 'Schedule Message';
497        schedulerButton.innerHTML = `
498            <svg viewBox="0 0 24 24">
499                <path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm0-12H5V6h14v2zm-7 5h5v5h-5z"/>
500            </svg>
501        `;
502        
503        schedulerButton.addEventListener('click', async () => {
504            const modal = createSchedulerModal();
505            document.body.appendChild(modal);
506            await updateScheduledList(modal);
507        });
508        
509        // Insert the button
510        attachmentArea.parentElement.insertBefore(schedulerButton, attachmentArea);
511        console.log('Scheduler button added successfully');
512    }
513
514    // Initialize the extension
515    async function init() {
516        console.log('Initializing WhatsApp Message Scheduler');
517        
518        // Check for scheduled messages every 10 seconds
519        setInterval(checkScheduledMessages, 10000);
520        
521        // Initial check
522        checkScheduledMessages();
523        
524        // Wait for page to load
525        const observer = new MutationObserver(debounce(() => {
526            addSchedulerButton();
527        }, 1000));
528        
529        observer.observe(document.body, {
530            childList: true,
531            subtree: true
532        });
533        
534        // Try to add button immediately
535        setTimeout(addSchedulerButton, 2000);
536    }
537
538    // Start when DOM is ready
539    if (document.readyState === 'loading') {
540        document.addEventListener('DOMContentLoaded', init);
541    } else {
542        init();
543    }
544})();
WhatsApp Message Scheduler | Robomonkey