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})();