WhatsApp Message Scheduler Pro

Open/search chats, start new by phone, schedule & auto-send messages with improved reliability

Size

76.7 KB

Version

3.2.8

Created

Nov 15, 2025

Updated

15 days ago

1// ==UserScript==
2// @name		WhatsApp Message Scheduler Pro
3// @description		Open/search chats, start new by phone, schedule & auto-send messages with improved reliability
4// @version		3.2.8
5// @match		https://*.web.whatsapp.com/*
6// @icon		https://web.whatsapp.com/favicon/1x/f54/v4/
7// @namespace		io.robomonkey.whatsapp.proscheduler
8// @author		Robomonkey
9// @grant		none
10// ==/UserScript==
11(function () {
12    'use strict';
13
14    const log = (...a) => console.log('[WAW Pro]', ...a);
15    const LS = { JOBS:'waw.jobs.v3', BOOK:'waw.addressbook.v1', PREFS:'waw.prefs.v1', PENDING:'waw.pending.v1', HISTORY:'waw.history.v1', FAB_POS:'waw.fabpos.v1', ARCHIVE:'waw.archive.v1' };
16    const load = (k, d) => { try { return JSON.parse(localStorage.getItem(k) || JSON.stringify(d)); } catch { return d; } };
17    const save = (k, v) => localStorage.setItem(k, JSON.stringify(v));
18    const loadJobs = () => load(LS.JOBS, []);
19    const saveJobs = (v) => save(LS.JOBS, v);
20    const loadBook = () => load(LS.BOOK, []);
21    const saveBook = (v) => save(LS.BOOK, v);
22    const loadPrefs = () => load(LS.PREFS, { panelOpen:false });
23    const savePrefs = (v) => save(LS.PREFS, v);
24    const loadHistory = () => load(LS.HISTORY, []);
25    const saveHistory = (v) => save(LS.HISTORY, v);
26    const loadArchive = () => load(LS.ARCHIVE, []);
27    const saveArchive = (v) => save(LS.ARCHIVE, v);
28    const loadFabPos = () => load(LS.FAB_POS, null);
29    const saveFabPos = (v) => save(LS.FAB_POS, v);
30    const uid = () => Math.random().toString(36).slice(2) + Date.now().toString(36);
31    const delay = (ms) => new Promise(r => setTimeout(r, ms));
32    const fmt = (ms) => new Date(ms).toLocaleString();
33    const fmtShort = (ms) => { const d = new Date(ms); return `${pad(d.getHours())}:${pad(d.getMinutes())}`; };
34    const pad = (n) => String(n).padStart(2, '0');
35    const isToday = (ms) => { const d = new Date(ms); const t = new Date(); return d.getDate()===t.getDate() && d.getMonth()===t.getMonth() && d.getFullYear()===t.getFullYear(); };
36
37    // Recurring messages helpers
38    function getNextOccurrence(baseTime, repeatType, weeklyDays = []) {
39        const base = new Date(baseTime);
40        
41        if (repeatType === 'daily') {
42            return base.getTime() + 24 * 60 * 60 * 1000;
43        }
44        
45        if (repeatType === 'weekly' && weeklyDays.length > 0) {
46            const currentDay = base.getDay();
47            const sortedDays = weeklyDays.map(Number).sort((a, b) => a - b);
48            
49            let nextDay = sortedDays.find(d => d > currentDay);
50            let daysToAdd;
51            
52            if (nextDay !== undefined) {
53                daysToAdd = nextDay - currentDay;
54            } else {
55                daysToAdd = 7 - currentDay + sortedDays[0];
56            }
57            
58            return base.getTime() + daysToAdd * 24 * 60 * 60 * 1000;
59        }
60        
61        return null;
62    }
63
64    // History management
65    function addToHistory(status, target, text, error=null){
66        const history = loadHistory();
67        history.push({ id: uid(), timestamp: Date.now(), status, target, text: text.substring(0, 100), error });
68        const weekAgo = Date.now() - 7*24*60*60*1000;
69        const filtered = history.filter(h => h.timestamp > weekAgo);
70        saveHistory(filtered);
71        
72        // Also add to permanent archive
73        const archive = loadArchive();
74        archive.push({ id: uid(), timestamp: Date.now(), status, target, text, error });
75        saveArchive(archive);
76    }
77    
78    function archiveJob(job) {
79        const archive = loadArchive();
80        archive.push({
81            id: uid(),
82            timestamp: job.when,
83            status: 'scheduled',
84            target: job.target.phone || job.target.name,
85            text: job.text,
86            repeat: job.repeat || null,
87            weeklyDays: job.weeklyDays || null,
88            archivedAt: Date.now()
89        });
90        saveArchive(archive);
91    }
92
93    function getStats(){
94        const history = loadHistory();
95        const today = history.filter(h => isToday(h.timestamp));
96        const jobs = loadJobs();
97        const pendingToday = jobs.filter(j => isToday(j.when));
98        return {
99            sentToday: today.filter(h => h.status === 'sent').length,
100            failedToday: today.filter(h => h.status === 'failed').length,
101            pendingToday: pendingToday.length,
102            pendingTotal: jobs.length,
103            recentSent: today.filter(h => h.status === 'sent').slice(-10).reverse()
104        };
105    }
106
107    function timeUntil(targetMs) {
108        const diff = targetMs - Date.now();
109        if (diff <= 0) return '⏰ Ready to send!';
110        
111        const days = Math.floor(diff / (1000 * 60 * 60 * 24));
112        const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
113        const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
114        const seconds = Math.floor((diff % (1000 * 60)) / 1000);
115        
116        if (days > 0) return `${days}d ${hours}h ${minutes}m`;
117        if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
118        if (minutes > 0) return `${minutes}m ${seconds}s`;
119        return `${seconds}s`;
120    }
121
122    const selectors = {
123        appRoot: '#app',
124        searchBtn: '[data-testid="chat-list-search"], [data-icon="search"], [aria-label*="Search"], [aria-label*="חיפוש"]',
125        searchBox: [
126            'header [contenteditable="true"][role="textbox"]',
127            'aside [contenteditable="true"][role="textbox"]',
128            'header [contenteditable="true"]',
129            'aside [contenteditable="true"]',
130            'input[type="text"]'
131        ],
132        chatListItem: ['[data-testid="cell-frame-container"]','[role="listitem"]','div[tabindex][aria-label]'],
133        chatNameInList: ['[data-testid*="chatlist"]','[dir="auto"]','span[title]','span'],
134        headerTitle: ['[data-testid="conversation-info-header-chat-title"]','header span[title]'],
135        composer: [
136            'footer div[contenteditable="true"][role="textbox"]',
137            'footer [contenteditable="true"][data-lexical-editor="true"]',
138            'footer [contenteditable="true"]'
139        ],
140        sendButton: [
141            'footer [data-testid="compose-btn-send"]',
142            'footer [aria-label*="Send"]',
143            'footer [data-icon="send"]'
144        ]
145    };
146
147    function qsAny(list, root=document){ for (const sel of list){ const el=root.querySelector(sel); if (el) return el; } return null; }
148    function getActiveChatTitle(){ const el = qsAny(selectors.headerTitle); return el ? (el.getAttribute('title')||el.textContent||'').trim() : null; }
149    function normalizePhone(num){ const d = String(num).replace(/[^\d]/g,''); return d.startsWith('00') ? d.slice(2) : d; }
150
151    async function waitFor(sel, { root=document, timeout=30000, check=150 }={}) {
152        const t0 = Date.now();
153        return new Promise((res, rej)=>{
154            const it = setInterval(()=>{
155                const el = root.querySelector(sel);
156                if (el) { clearInterval(it); res(el); }
157                else if (Date.now()-t0 > timeout) { clearInterval(it); rej(new Error('Timeout: '+sel)); }
158            }, check);
159        });
160    }
161
162    function findSearchBox(){
163        const btn = document.querySelector(selectors.searchBtn);
164        if (btn) try { btn.click(); } catch {}
165        return qsAny(selectors.searchBox);
166    }
167
168    async function typeInto(el, text){
169        el.focus();
170        try { document.execCommand('selectAll', false, null); document.execCommand('delete', false, null); } catch {}
171        for (const ch of text){
172            el.dispatchEvent(new InputEvent('beforeinput', { inputType:'insertText', data:ch, bubbles:true }));
173            if ('value' in el) el.value += ch; else el.textContent = (el.textContent||'') + ch;
174            el.dispatchEvent(new InputEvent('input', { bubbles:true, data:ch }));
175            await delay(8);
176        }
177    }
178
179    function getSearchResults(){
180        const rows = [];
181        const containers = document.querySelectorAll(selectors.chatListItem.join(','));
182        containers.forEach(row=>{
183            let name=''; let nameEl=null;
184            for (const sel of selectors.chatNameInList){ const c=row.querySelector(sel); if (c){ nameEl=c; break; } }
185            if (nameEl) name = (nameEl.getAttribute('title')||nameEl.textContent||'').trim();
186            if (name) rows.push({ row, name });
187        });
188        const seen=new Set(), uniq=[];
189        for (const it of rows){ if (!seen.has(it.name)){ uniq.push(it); seen.add(it.name); } }
190        return uniq;
191    }
192
193    async function openChatByName(name, { exact=false }={}){
194        const box = findSearchBox(); if (!box) throw new Error('Search box not found');
195        await typeInto(box, name); await delay(500);
196        let results = getSearchResults(); if (!results.length){ await delay(700); results = getSearchResults(); }
197        if (!results.length) throw new Error('No matches');
198        let pick = results.find(r=>r.name.toLowerCase()===name.toLowerCase());
199        if (!pick && !exact) pick = results.find(r=>r.name.toLowerCase().includes(name.toLowerCase())) || results[0];
200        if (!pick) pick = results[0];
201        pick.row.click();
202        await delay(700);
203        try { await typeInto(box, ''); } catch {}
204        return getActiveChatTitle() || name;
205    }
206
207    async function openChatByPhone(phone, presetText){
208        const p = normalizePhone(phone);
209        const url = new URL('https://web.whatsapp.com/send'); url.searchParams.set('phone', p);
210        if (presetText) url.searchParams.set('text', presetText);
211        window.location.assign(url.toString());
212        await waitFor(selectors.appRoot, { timeout:60000 }).catch(()=>{});
213        await waitFor(selectors.composer.map(s=>s).join(','), { timeout:60000 });
214        await delay(800);
215        return getActiveChatTitle() || p;
216    }
217
218    function visible(el){ return !!(el && el.offsetParent !== null); }
219
220    async function getComposer({ requireVisible=true }={}){
221        for (const sel of selectors.composer){
222            const ed = document.querySelector(sel);
223            if (ed && (!requireVisible || visible(ed))) return ed;
224        }
225        return null;
226    }
227
228    function setTextViaSelection(ed, text){
229        try {
230            const r = document.createRange(); r.selectNodeContents(ed);
231            const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(r);
232            document.execCommand('delete', false, null);
233        } catch {}
234        let usedExec = false;
235        if (document.queryCommandSupported && document.queryCommandSupported('insertText')){
236            usedExec = document.execCommand('insertText', false, text);
237        }
238        if (!usedExec){
239            ed.dispatchEvent(new InputEvent('beforeinput', { inputType:'insertFromPaste', data:text, bubbles:true }));
240            ed.textContent = text;
241            ed.dispatchEvent(new InputEvent('input', { bubbles:true, data:text }));
242        }
243    }
244
245    async function clickSendButtonIfAny(){
246        const btn = qsAny(selectors.sendButton);
247        if (btn){ btn.click(); await delay(120); return true; }
248        return false;
249    }
250
251    async function pressEnter(ed){
252        const kd = new KeyboardEvent('keydown', { bubbles:true, cancelable:true, key:'Enter', code:'Enter', keyCode:13 });
253        const ku = new KeyboardEvent('keyup', { bubbles:true, key:'Enter', code:'Enter', keyCode:13 });
254        ed.dispatchEvent(kd); ed.dispatchEvent(ku);
255        await delay(120);
256    }
257
258    async function ensureFocused(){
259        window.focus();
260        document.body.click();
261        await delay(50);
262    }
263
264    async function sendMessage(text, { retries=3 }={}){
265        for (let attempt=1; attempt<=retries; attempt++){
266            await ensureFocused();
267            let ed = await getComposer({ requireVisible:true });
268            if (!ed){ await delay(300); ed = await getComposer({ requireVisible:false }); }
269            if (!ed){ if (attempt===retries) throw new Error('Composer not found'); await delay(250); continue; }
270
271            ed.focus();
272            setTextViaSelection(ed, text);
273
274            const clicked = await clickSendButtonIfAny();
275            if (!clicked) await pressEnter(ed);
276
277            await delay(200);
278            const remaining = (ed.textContent||'').trim();
279            if (remaining.length === 0) return true;
280
281            await delay(200);
282        }
283        const edFinal = await getComposer({ requireVisible:false });
284        if (edFinal){
285            await pressEnter(edFinal); await delay(200);
286            if ((edFinal.textContent||'').trim().length === 0) return true;
287        }
288        throw new Error('Failed to send (text stayed in composer)');
289    }
290
291    const styles = `
292  .waw-panel{position:fixed;right:16px;bottom:16px;z-index:999999;width:420px;background:linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);color:#fff;border-radius:20px;padding:0;box-shadow:0 20px 60px rgba(0,0,0,.5), 0 0 0 1px rgba(255,255,255,.1);font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Arial;backdrop-filter:blur(10px)}
293  .waw-header{display:flex;justify-content:space-between;align-items:center;padding:20px 20px 16px 20px;background:linear-gradient(135deg, #25D366 0%, #20ba5a 100%);border-radius:20px 20px 0 0;box-shadow:0 4px 12px rgba(37,211,102,.3);cursor:move;user-select:none}
294  .waw-title{font-weight:900;font-size:16px;color:#fff;text-shadow:0 2px 4px rgba(0,0,0,.2);letter-spacing:0.3px;pointer-events:none}
295  .waw-close{background:rgba(255,255,255,.2);border:none;color:#fff;cursor:pointer;font-size:18px;width:32px;height:32px;border-radius:50%;transition:all .2s;display:flex;align-items:center;justify-content:center;font-weight:700}
296  .waw-close:hover{background:rgba(255,255,255,.3);transform:rotate(90deg)}
297  .waw-content{padding:20px;max-height:520px;overflow-y:auto}
298  .waw-content::-webkit-scrollbar{width:8px}
299  .waw-content::-webkit-scrollbar-track{background:rgba(255,255,255,.05);border-radius:10px}
300  .waw-content::-webkit-scrollbar-thumb{background:rgba(37,211,102,.4);border-radius:10px}
301  .waw-content::-webkit-scrollbar-thumb:hover{background:rgba(37,211,102,.6)}
302  .waw-tabs{display:flex;gap:8px;margin-top:10px}
303  .waw-tab{flex:1;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);color:#e5e7eb;border-radius:12px;padding:10px;text-align:center;cursor:pointer;font-weight:700;transition:all .3s}
304  .waw-tab.active{background:#25D366;color:#111;border-color:#25D366;box-shadow:0 4px 12px rgba(37,211,102,.3)}
305  .waw-section{margin-top:16px;background:rgba(255,255,255,.03);border-radius:16px;padding:16px;border:1px solid rgba(255,255,255,.08)}
306  .waw-field{margin:12px 0}
307  .waw-field label{display:block;font-size:13px;color:#cbd5e1;margin-bottom:6px;font-weight:600}
308  .waw-input,.waw-textarea,.waw-dt{width:100%;background:rgba(255,255,255,.08);color:#fff;border:1px solid rgba(255,255,255,.15);border-radius:12px;padding:12px;outline:none;transition:all .3s;font-size:14px}
309  .waw-input:focus,.waw-textarea:focus,.waw-dt:focus{border-color:#25D366;box-shadow:0 0 0 3px rgba(37,211,102,.15);background:rgba(255,255,255,.12)}
310  .waw-input::placeholder,.waw-textarea::placeholder{color:rgba(255,255,255,.4)}
311  .waw-row{display:flex;gap:8px}
312  .waw-btn{background:linear-gradient(135deg, #25D366 0%, #20ba5a 100%);color:#fff;border:none;padding:12px 20px;border-radius:12px;cursor:pointer;font-weight:800;transition:all .3s;box-shadow:0 4px 12px rgba(37,211,102,.3);font-size:14px}
313  .waw-btn:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(37,211,102,.4)}
314  .waw-btn:active{transform:translateY(0)}
315  .waw-btn.secondary{background:rgba(255,255,255,.1);color:#fff;box-shadow:none;border:1px solid rgba(255,255,255,.15)}
316  .waw-btn.secondary:hover{background:rgba(255,255,255,.15);transform:translateY(-1px)}
317  .waw-small{font-size:12px;color:#9aa7b2}
318  .waw-list{max-height:200px;overflow:auto;border-top:1px solid rgba(255,255,255,.1);padding-top:8px;margin-top:8px}
319  .waw-list::-webkit-scrollbar{width:6px}
320  .waw-list::-webkit-scrollbar-track{background:rgba(255,255,255,.05);border-radius:10px}
321  .waw-list::-webkit-scrollbar-thumb{background:rgba(37,211,102,.3);border-radius:10px}
322  .waw-item{display:flex;justify-content:space-between;gap:12px;padding:12px;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.02);border-radius:8px;margin-bottom:8px;transition:all .2s}
323  .waw-item:hover{background:rgba(255,255,255,.05);transform:translateX(-2px)}
324  .waw-item:last-child{border-bottom:none}
325  .waw-fab{position:fixed;right:16px;bottom:16px;width:64px;height:64px;border-radius:50%;border:none;background:linear-gradient(135deg, #25D366 0%, #20ba5a 100%);color:#fff;font-weight:900;cursor:pointer;z-index:999998;box-shadow:0 8px 24px rgba(37,211,102,.4), 0 0 0 0 rgba(37,211,102,.4);font-size:28px;transition:all .3s;display:flex;align-items:center;justify-content:center}
326  .waw-fab:hover{transform:scale(1.1) rotate(10deg);box-shadow:0 12px 32px rgba(37,211,102,.5)}
327  .waw-fab:active{transform:scale(0.95)}
328  @keyframes pulse{0%{box-shadow:0 8px 24px rgba(37,211,102,.4), 0 0 0 0 rgba(37,211,102,.4)}50%{box-shadow:0 8px 24px rgba(37,211,102,.4), 0 0 0 12px rgba(37,211,102,0)}100%{box-shadow:0 8px 24px rgba(37,211,102,.4), 0 0 0 0 rgba(37,211,102,0)}}
329  .waw-fab.has-jobs{animation:pulse 2s infinite}
330  .waw-diagnostics{background:#111827;border:1px solid #374151;border-radius:12px;padding:12px;margin-top:8px}
331  .waw-diagnostics div{font-size:12px;margin:4px 0}
332  .waw-divider{border-top:2px solid rgba(255,255,255,.1);padding-top:16px;margin-top:16px;position:relative}
333  .waw-divider::before{content:'';position:absolute;top:-1px;left:50%;transform:translateX(-50%);width:60px;height:2px;background:linear-gradient(90deg, transparent, #25D366, transparent)}
334  .waw-section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
335  .waw-section-title{font-weight:800;font-size:14px;color:#25D366;display:flex;align-items:center;gap:6px}
336  .waw-panel.dragging{transition:none;opacity:0.9}
337  `;
338    
339    function el(t,a={},h=''){ const e=document.createElement(t); Object.entries(a).forEach(([k,v])=>e.setAttribute(k,v)); if(h) e.innerHTML=h; return e; }
340    function injectStyles(){ const s=document.createElement('style'); s.textContent=styles; document.head.appendChild(s); }
341
342    // Notification system
343    function showNotification(title, message, type = 'info') {
344        const notification = el('div', {
345            style: `position:fixed;top:20px;right:20px;z-index:1000000;background:${type === 'success' ? 'linear-gradient(135deg, #25D366 0%, #20ba5a 100%)' : type === 'error' ? 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)' : 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)'};color:#fff;padding:16px 20px;border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.3);min-width:300px;max-width:400px;animation:slideIn 0.3s ease-out;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Arial`
346        });
347        
348        notification.innerHTML = `
349            <div style="display:flex;justify-content:space-between;align-items:start;gap:12px">
350                <div style="flex:1">
351                    <div style="font-weight:800;font-size:14px;margin-bottom:6px">${title}</div>
352                    <div style="font-size:12px;opacity:0.95;line-height:1.4">${message}</div>
353                </div>
354                <button style="background:rgba(255,255,255,.2);border:none;color:#fff;cursor:pointer;font-size:16px;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;flex-shrink:0" onclick="this.parentElement.parentElement.remove()"></button>
355            </div>
356        `;
357        
358        if (!document.getElementById('notification-styles')) {
359            const style = el('style', {id: 'notification-styles'});
360            style.textContent = `
361                @keyframes slideIn {
362                    from { transform: translateX(400px); opacity: 0; }
363                    to { transform: translateX(0); opacity: 1; }
364                }
365                @keyframes slideOut {
366                    from { transform: translateX(0); opacity: 1; }
367                    to { transform: translateX(400px); opacity: 0; }
368                }
369            `;
370            document.head.appendChild(style);
371        }
372        
373        document.body.appendChild(notification);
374        
375        setTimeout(() => {
376            notification.style.animation = 'slideOut 0.3s ease-in';
377            setTimeout(() => notification.remove(), 300);
378        }, 5000);
379    }
380
381    function createPanel(){
382        const panel = el('div',{class:'waw-panel'}); panel.style.display='none';
383        panel.innerHTML = `
384      <div class="waw-header">
385        <div class="waw-title">WhatsApp Message Scheduler</div>
386        <button class="waw-close" title="Close"></button>
387      </div>
388      
389      <div class="waw-content" style="max-height:500px;overflow-y:auto;margin-top:10px">
390        
391        <!-- Daily Stats Dashboard -->
392        <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:16px">
393          <div style="background:rgba(37,211,102,.15);border:1px solid rgba(37,211,102,.3);border-radius:12px;padding:12px;text-align:center;cursor:pointer;transition:all .2s" id="stat-sent-card" title="Click to see details">
394            <div style="font-size:24px;font-weight:900;color:#25D366" id="stat-sent">0</div>
395            <div class="waw-small" style="color:#25D366;font-weight:600">✅ Sent Today</div>
396          </div>
397          <div style="background:rgba(239,68,68,.15);border:1px solid rgba(239,68,68,.3);border-radius:12px;padding:12px;text-align:center;cursor:pointer;transition:all .2s" id="stat-failed-card" title="Click to see details">
398            <div style="font-size:24px;font-weight:900;color:#ef4444" id="stat-failed">0</div>
399            <div class="waw-small" style="color:#ef4444;font-weight:600">❌ Failed Today</div>
400          </div>
401          <div style="background:rgba(59,130,246,.15);border:1px solid rgba(59,130,246,.3);border-radius:12px;padding:12px;text-align:center;cursor:pointer;transition:all .2s" id="stat-pending-card" title="Click to see details">
402            <div style="font-size:24px;font-weight:900;color:#3b82f6" id="stat-pending">0</div>
403            <div class="waw-small" style="color:#3b82f6;font-weight:600">⏳ Pending</div>
404          </div>
405        </div>
406        
407        <!-- Details Modal -->
408        <div id="details-modal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.8);z-index:1000000;align-items:center;justify-content:center">
409          <div style="background:linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);border-radius:16px;padding:20px;max-width:500px;width:90%;max-height:80vh;overflow:auto;box-shadow:0 20px 60px rgba(0,0,0,.5)">
410            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
411              <h3 id="modal-title" style="margin:0;color:#25D366;font-size:18px"></h3>
412              <button class="waw-close" id="close-modal" style="position:static"></button>
413            </div>
414            <div id="modal-content" style="max-height:60vh;overflow:auto"></div>
415          </div>
416        </div>
417        
418        <div class="waw-field">
419          <label>📱 To: (search name or enter phone)</label>
420          <input class="waw-input" id="sch-search" placeholder="Type name or phone number...">
421          <div class="waw-list" id="sch-results" style="max-height:120px;display:none;margin-top:4px;border:1px solid #2a3942;border-radius:8px;background:#0b141a"></div>
422        </div>
423        
424        <div class="waw-field">
425          <label>💬 Message</label>
426          <textarea class="waw-textarea" id="sch-text" rows="3" placeholder="Type your message..."></textarea>
427        </div>
428        
429        <div class="waw-field">
430          <label>⏰ Send at</label>
431          <input class="waw-dt" id="sch-when" type="datetime-local">
432        </div>
433        
434        <div class="waw-field">
435          <label>🔄 Repeat (Optional)</label>
436          <select class="waw-input" id="sch-repeat" style="cursor:pointer">
437            <option value="">One time only</option>
438            <option value="daily">Daily</option>
439            <option value="weekly">Weekly</option>
440          </select>
441        </div>
442        
443        <div class="waw-field" id="weekly-days-field" style="display:none">
444          <label>📅 Repeat on days</label>
445          <div style="display:flex;gap:6px;flex-wrap:wrap">
446            <label style="display:flex;align-items:center;gap:4px;cursor:pointer;background:rgba(255,255,255,.05);padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.1);transition:all .2s">
447              <input type="checkbox" class="day-checkbox" value="0" style="cursor:pointer">
448              <span style="font-size:12px">Sun</span>
449            </label>
450            <label style="display:flex;align-items:center;gap:4px;cursor:pointer;background:rgba(255,255,255,.05);padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.1);transition:all .2s">
451              <input type="checkbox" class="day-checkbox" value="1" style="cursor:pointer">
452              <span style="font-size:12px">Mon</span>
453            </label>
454            <label style="display:flex;align-items:center;gap:4px;cursor:pointer;background:rgba(255,255,255,.05);padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.1);transition:all .2s">
455              <input type="checkbox" class="day-checkbox" value="2" style="cursor:pointer">
456              <span style="font-size:12px">Tue</span>
457            </label>
458            <label style="display:flex;align-items:center;gap:4px;cursor:pointer;background:rgba(255,255,255,.05);padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.1);transition:all .2s">
459              <input type="checkbox" class="day-checkbox" value="3" style="cursor:pointer">
460              <span style="font-size:12px">Wed</span>
461            </label>
462            <label style="display:flex;align-items:center;gap:4px;cursor:pointer;background:rgba(255,255,255,.05);padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.1);transition:all .2s">
463              <input type="checkbox" class="day-checkbox" value="4" style="cursor:pointer">
464              <span style="font-size:12px">Thu</span>
465            </label>
466            <label style="display:flex;align-items:center;gap:4px;cursor:pointer;background:rgba(255,255,255,.05);padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.1);transition:all .2s">
467              <input type="checkbox" class="day-checkbox" value="5" style="cursor:pointer">
468              <span style="font-size:12px">Fri</span>
469            </label>
470            <label style="display:flex;align-items:center;gap:4px;cursor:pointer;background:rgba(255,255,255,.05);padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.1);transition:all .2s">
471              <input type="checkbox" class="day-checkbox" value="6" style="cursor:pointer">
472              <span style="font-size:12px">Sat</span>
473            </label>
474          </div>
475        </div>
476        
477        <button class="waw-btn" id="btn-schedule" style="width:100%;margin-bottom:16px">Schedule Message</button>
478        
479        <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">
480          <button class="waw-btn secondary" id="btn-send-now" style="width:100%">🚀 Send Now</button>
481          <button class="waw-btn secondary" id="btn-clear-form" style="width:100%">🗑️ Clear</button>
482        </div>
483        
484        <div style="border-top:2px solid #2a3942;padding-top:12px;margin-top:12px">
485          <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
486            <label style="font-weight:700;font-size:13px">📅 Scheduled Messages</label>
487            <button class="waw-btn secondary" id="btn-refreshjobs" style="padding:6px 12px;font-size:11px">Refresh</button>
488          </div>
489          <div class="waw-list" id="jobs-list"></div>
490        </div>
491        
492        <!-- Bulk Import Section -->
493        <div style="border-top:2px solid #2a3942;padding-top:12px;margin-top:12px">
494          <label style="font-weight:700;font-size:13px;display:block;margin-bottom:8px;color:#3b82f6">📂 Bulk Import from CSV</label>
495          <div class="waw-small" style="margin-bottom:8px;opacity:0.7">Format: target,message,datetime (e.g., John,Hello,2024-12-31 14:30)</div>
496          <input type="file" id="csv-file" accept=".csv,.txt" style="display:none">
497          <button class="waw-btn secondary" id="btn-import-csv" style="width:100%;margin-bottom:8px">📥 Choose CSV File</button>
498          <div id="import-preview" style="display:none;background:rgba(59,130,246,.1);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:12px;margin-top:8px">
499            <div style="font-weight:700;margin-bottom:8px;color:#3b82f6">Preview:</div>
500            <div id="import-preview-content" style="max-height:120px;overflow:auto;font-size:11px"></div>
501            <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px">
502              <button class="waw-btn" id="btn-confirm-import" style="padding:8px">✅ Import All</button>
503              <button class="waw-btn secondary" id="btn-cancel-import" style="padding:8px">❌ Cancel</button>
504            </div>
505          </div>
506        </div>
507        
508        <!-- Recent Sent Messages -->
509        <div style="border-top:2px solid #2a3942;padding-top:12px;margin-top:12px" id="recent-sent-section">
510          <label style="font-weight:700;font-size:13px;display:block;margin-bottom:8px;color:#25D366">📨 Recently Sent Today</label>
511          <div id="recent-sent-list" style="max-height:150px;overflow:auto"></div>
512        </div>
513        
514        <!-- Export by Date Range Section -->
515        <div style="border-top:2px solid #2a3942;padding-top:12px;margin-top:12px">
516          <label style="font-weight:700;font-size:13px;display:block;margin-bottom:8px;color:#f59e0b">📅 Export by Date Range</label>
517          <button class="waw-btn secondary" id="btn-export-range" style="width:100%">📅 Export by Date Range</button>
518        </div>
519        
520        <!-- Export Modal -->
521        <div id="export-modal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.8);z-index:1000001;align-items:center;justify-content:center">
522          <div style="background:linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);border-radius:16px;padding:20px;max-width:450px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,.5)">
523            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
524              <h3 style="margin:0;color:#f59e0b;font-size:18px">📅 Export by Date Range</h3>
525              <button class="waw-close" id="close-export-modal" style="position:static"></button>
526            </div>
527            
528            <div style="margin-bottom:16px">
529              <label style="display:block;font-size:13px;color:#cbd5e1;margin-bottom:6px;font-weight:600">📆 Start Date</label>
530              <input class="waw-dt" id="export-start-date" type="date" style="width:100%">
531            </div>
532            
533            <div style="margin-bottom:16px">
534              <label style="display:block;font-size:13px;color:#cbd5e1;margin-bottom:6px;font-weight:600">📆 End Date</label>
535              <input class="waw-dt" id="export-end-date" type="date" style="width:100%">
536            </div>
537            
538            <div style="margin-bottom:16px">
539              <label style="display:block;font-size:13px;color:#cbd5e1;margin-bottom:8px;font-weight:600">📋 Include:</label>
540              <div style="display:flex;gap:12px">
541                <label style="display:flex;align-items:center;gap:6px;cursor:pointer;background:rgba(255,255,255,.05);padding:10px 16px;border-radius:8px;border:1px solid rgba(255,255,255,.1);flex:1">
542                  <input type="checkbox" id="export-include-history" checked style="cursor:pointer">
543                  <span style="font-size:13px">History (Sent/Failed)</span>
544                </label>
545                <label style="display:flex;align-items:center;gap:6px;cursor:pointer;background:rgba(255,255,255,.05);padding:10px 16px;border-radius:8px;border:1px solid rgba(255,255,255,.1);flex:1">
546                  <input type="checkbox" id="export-include-scheduled" checked style="cursor:pointer">
547                  <span style="font-size:13px">Scheduled</span>
548                </label>
549              </div>
550              <div style="margin-top:8px">
551                <label style="display:flex;align-items:center;gap:6px;cursor:pointer;background:rgba(255,255,255,.05);padding:10px 16px;border-radius:8px;border:1px solid rgba(255,255,255,.1)">
552                  <input type="checkbox" id="export-include-archive" checked style="cursor:pointer">
553                  <span style="font-size:13px">Archive (Deleted/Dismissed)</span>
554                </label>
555              </div>
556            </div>
557
558            <div id="export-preview-section" style="display:none;margin-bottom:16px;background:rgba(245,158,11,.1);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:12px">
559              <div style="font-weight:700;margin-bottom:6px;color:#f59e0b;font-size:13px">Preview:</div>
560              <div id="export-preview-text" style="font-size:12px;opacity:0.9"></div>
561            </div>
562            
563            <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
564              <button class="waw-btn" id="btn-export-json" style="padding:10px">📄 Export JSON</button>
565              <button class="waw-btn" id="btn-export-csv" style="padding:10px">📊 Export CSV</button>
566            </div>
567          </div>
568        </div>
569        
570        <div style="border-top:2px solid #2a3942;padding-top:12px;margin-top:12px">
571          <label style="font-weight:700;font-size:13px;display:block;margin-bottom:8px">📞 Address Book</label>
572          <div class="waw-row" style="margin-bottom:8px">
573            <input class="waw-input" id="book-label" placeholder="Name" style="flex:1">
574            <input class="waw-input" id="book-phone" placeholder="Phone" style="flex:1">
575            <button class="waw-btn" id="book-save" style="padding:8px 16px">Save</button>
576          </div>
577          <div class="waw-list" id="book-list"></div>
578        </div>
579        
580        <div class="waw-small" style="text-align:center;margin-top:12px;padding-top:12px;border-top:1px solid #2a3942">Keep this tab open & logged-in for auto-send</div>
581      </div>
582    `;
583        document.body.appendChild(panel);
584        const fab = el('button',{class:'waw-fab',title:'Open Pro Scheduler'},'⏰'); document.body.appendChild(fab);
585
586        // Restore FAB position from localStorage
587        const savedFabPos = loadFabPos();
588        if (savedFabPos) {
589            fab.style.left = savedFabPos.left;
590            fab.style.top = savedFabPos.top;
591            fab.style.right = 'auto';
592            fab.style.bottom = 'auto';
593        }
594
595        fab.onclick = ()=>{
596            panel.style.display = panel.style.display==='none' ? 'block' : 'none';
597            const p = loadPrefs(); p.panelOpen = (panel.style.display!=='none'); savePrefs(p);
598            if (p.panelOpen){ refreshJobs(); renderBook(); prefillDate(); }
599        };
600        panel.querySelector('.waw-close').onclick = ()=>{
601            panel.style.display='none'; const p=loadPrefs(); p.panelOpen=false; savePrefs(p);
602        };
603
604        // FAB drag functionality - only with Shift key held
605        let fabDragging = false;
606        let fabCurrentX, fabCurrentY, fabInitialX, fabInitialY;
607
608        fab.addEventListener('mousedown', (e) => {
609            // Only allow dragging if Shift key is held
610            if (!e.shiftKey) return;
611            
612            e.preventDefault();
613            fabDragging = true;
614            fab.style.cursor = 'grabbing';
615            fab.style.transition = 'none';
616            fabInitialX = e.clientX - fab.offsetLeft;
617            fabInitialY = e.clientY - fab.offsetTop;
618        });
619
620        document.addEventListener('mousemove', (e) => {
621            if (!fabDragging) return;
622            e.preventDefault();
623            fabCurrentX = e.clientX - fabInitialX;
624            fabCurrentY = e.clientY - fabInitialY;
625            fab.style.left = fabCurrentX + 'px';
626            fab.style.top = fabCurrentY + 'px';
627            fab.style.right = 'auto';
628            fab.style.bottom = 'auto';
629        });
630
631        document.addEventListener('mouseup', () => {
632            if (fabDragging) {
633                fabDragging = false;
634                fab.style.cursor = 'grab';
635                fab.style.transition = 'all .3s';
636                saveFabPos({
637                    left: fab.style.left,
638                    top: fab.style.top
639                });
640            }
641        });
642
643        fab.style.cursor = 'grab';
644        fab.title = 'Click to open | Hold Shift + Drag to move';
645
646        const modal = panel.querySelector('#details-modal');
647        const modalTitle = panel.querySelector('#modal-title');
648        const modalContent = panel.querySelector('#modal-content');
649        const closeModal = panel.querySelector('#close-modal');
650        
651        closeModal.onclick = () => { modal.style.display = 'none'; };
652        modal.onclick = (e) => { if (e.target === modal) modal.style.display = 'none'; };
653        
654        function showModal(title, items, type) {
655            modalTitle.textContent = title;
656            modalContent.innerHTML = '';
657            
658            if (items.length === 0) {
659                modalContent.innerHTML = '<div class="waw-small" style="text-align:center;padding:20px;opacity:0.5">No items to display</div>';
660            } else {
661                items.forEach(item => {
662                    const itemEl = el('div', {style: 'padding:12px;background:rgba(255,255,255,.05);border-radius:8px;margin-bottom:8px;border-left:3px solid ' + (type === 'sent' ? '#25D366' : type === 'failed' ? '#ef4444' : '#3b82f6')});
663                    
664                    if (type === 'pending') {
665                        const tgt = item.target.phone ? `📞 ${item.target.phone}` : `👤 ${item.target.name}`;
666                        itemEl.innerHTML = `
667                            <div style="display:flex;justify-content:space-between;margin-bottom:6px">
668                                <span style="font-weight:700;color:#3b82f6">${tgt}</span>
669                                <span style="opacity:0.7;font-size:11px">${fmt(item.when)}</span>
670                            </div>
671                            <div class="waw-small" style="color:#3b82f6;font-weight:600;margin-bottom:4px">${timeUntil(item.when)}</div>
672                            <div style="opacity:0.8;font-size:14px">${item.text}</div>
673                        `;
674                    } else {
675                        itemEl.innerHTML = `
676                            <div style="display:flex;justify-content:space-between;margin-bottom:6px">
677                                <span style="font-weight:700;color:${type === 'sent' ? '#25D366' : '#ef4444'}">${item.target}</span>
678                                <span style="opacity:0.7;font-size:11px">${fmt(item.timestamp)}</span>
679                            </div>
680                            <div style="opacity:0.8;font-size:14px;margin-bottom:${item.error ? '6px' : '0'}">${item.text}</div>
681                            ${item.error ? `<div style="color:#ef4444;font-size:11px;background:rgba(239,68,68,.1);padding:6px;border-radius:4px">❌ Error: ${item.error}</div>` : ''}
682                        `;
683                    }
684                    
685                    modalContent.appendChild(itemEl);
686                });
687            }
688            
689            modal.style.display = 'flex';
690        }
691        
692        panel.querySelector('#stat-sent-card').onclick = () => {
693            const history = loadHistory();
694            const sentToday = history.filter(h => isToday(h.timestamp) && h.status === 'sent');
695            showModal('✅ Messages Sent Today', sentToday, 'sent');
696        };
697        
698        panel.querySelector('#stat-failed-card').onclick = () => {
699            const history = loadHistory();
700            const failedToday = history.filter(h => isToday(h.timestamp) && h.status === 'failed');
701            showModal('❌ Failed Messages Today', failedToday, 'failed');
702        };
703        
704        panel.querySelector('#stat-pending-card').onclick = () => {
705            const jobs = loadJobs().sort((a,b)=>a.when-b.when);
706            showModal('⏳ Pending Messages', jobs, 'pending');
707        };
708
709        const header = panel.querySelector('.waw-header');
710        let isDragging = false;
711        let currentX, currentY, initialX, initialY;
712
713        header.addEventListener('mousedown', (e) => {
714            if (e.target.closest('.waw-close')) return;
715            isDragging = true;
716            panel.classList.add('dragging');
717            initialX = e.clientX - panel.offsetLeft;
718            initialY = e.clientY - panel.offsetTop;
719        });
720
721        document.addEventListener('mousemove', (e) => {
722            if (!isDragging) return;
723            e.preventDefault();
724            currentX = e.clientX - initialX;
725            currentY = e.clientY - initialY;
726            panel.style.left = currentX + 'px';
727            panel.style.top = currentY + 'px';
728            panel.style.right = 'auto';
729            panel.style.bottom = 'auto';
730        });
731
732        document.addEventListener('mouseup', () => {
733            if (isDragging) {
734                isDragging = false;
735                panel.classList.remove('dragging');
736            }
737        });
738
739        let selectedTarget = null;
740        const searchInput = panel.querySelector('#sch-search');
741        const resultsBox = panel.querySelector('#sch-results');
742        
743        const repeatSelect = panel.querySelector('#sch-repeat');
744        const weeklyDaysField = panel.querySelector('#weekly-days-field');
745        
746        repeatSelect.onchange = () => {
747            if (repeatSelect.value === 'weekly') {
748                weeklyDaysField.style.display = 'block';
749            } else {
750                weeklyDaysField.style.display = 'none';
751            }
752        };
753        
754        searchInput.oninput = ()=>{
755            const query = searchInput.value.trim().toLowerCase();
756            if (!query) {
757                resultsBox.style.display = 'none';
758                resultsBox.innerHTML = '';
759                selectedTarget = null;
760                return;
761            }
762            
763            const chats = getSearchResults();
764            const book = loadBook();
765            const results = [];
766            
767            chats.forEach(c => {
768                if (c.name.toLowerCase().includes(query)) {
769                    results.push({ type: 'chat', label: c.name, value: c.name });
770                }
771            });
772            
773            book.forEach(b => {
774                if (b.label.toLowerCase().includes(query) || b.phone.includes(query)) {
775                    results.push({ type: 'contact', label: b.label + ' (' + b.phone + ')', value: b.phone, isPhone: true });
776                }
777            });
778            
779            const digitsOnly = query.replace(/[^\d]/g, '');
780            if (digitsOnly.length >= 8) {
781                results.push({ type: 'phone', label: '📞 New number: ' + digitsOnly, value: digitsOnly, isPhone: true });
782            }
783            
784            if (results.length === 0) {
785                resultsBox.style.display = 'none';
786                resultsBox.innerHTML = '';
787            } else {
788                resultsBox.style.display = 'block';
789                resultsBox.innerHTML = '';
790                results.forEach(r => {
791                    const item = el('div', {class: 'waw-item', style: 'cursor:pointer;padding:8px;border-bottom:1px solid #2a3942'});
792                    item.textContent = r.label;
793                    item.onclick = ()=>{
794                        selectedTarget = r;
795                        searchInput.value = r.label;
796                        resultsBox.style.display = 'none';
797                    };
798                    resultsBox.appendChild(item);
799                });
800            }
801        };
802
803        panel.querySelector('#btn-schedule').onclick = ()=>{
804            const text  = panel.querySelector('#sch-text').value.trim();
805            const whenStr = panel.querySelector('#sch-when').value;
806            const repeatType = panel.querySelector('#sch-repeat').value;
807            
808            if (!text) return alert('Message required');
809            if (!whenStr) return alert('Choose date & time');
810            const when = Date.parse(whenStr); 
811            if (isNaN(when) || when<=Date.now()) return alert('Choose a future time');
812            if (!selectedTarget) return alert('Please select a contact or enter a phone number');
813            
814            let weeklyDays = [];
815            if (repeatType === 'weekly') {
816                const checkboxes = panel.querySelectorAll('.day-checkbox:checked');
817                weeklyDays = Array.from(checkboxes).map(cb => cb.value);
818                if (weeklyDays.length === 0) {
819                    return alert('Please select at least one day for weekly repeat');
820                }
821            }
822            
823            const job = { 
824                id: uid(), 
825                when, 
826                text, 
827                target: selectedTarget.isPhone 
828                    ? { name: null, phone: normalizePhone(selectedTarget.value) }
829                    : { name: selectedTarget.value, phone: null },
830                repeat: repeatType || null,
831                weeklyDays: weeklyDays.length > 0 ? weeklyDays : null
832            };
833            
834            const jobs = loadJobs(); 
835            jobs.push(job); 
836            saveJobs(jobs);
837            
838            panel.querySelector('#sch-text').value='';
839            panel.querySelector('#sch-repeat').value='';
840            weeklyDaysField.style.display = 'none';
841            panel.querySelectorAll('.day-checkbox').forEach(cb => cb.checked = false);
842            searchInput.value = '';
843            selectedTarget = null;
844            
845            refreshJobs(); 
846            alert(repeatType ? `Scheduled with ${repeatType} repeat!` : 'Scheduled');
847        };
848        
849        panel.querySelector('#btn-send-now').onclick = async ()=>{
850            const text = panel.querySelector('#sch-text').value.trim();
851            if (!text) return alert('Message required');
852            if (!selectedTarget) return alert('Please select a contact or enter a phone number');
853            
854            const job = {
855                id: uid(),
856                text,
857                target: selectedTarget.isPhone 
858                    ? { name: null, phone: normalizePhone(selectedTarget.value) }
859                    : { name: selectedTarget.value, phone: null }
860            };
861            
862            try {
863                await performJob(job);
864                const tgt = job.target.phone || job.target.name;
865                addToHistory('sent', tgt, job.text);
866                
867                showNotification(
868                    '✅ Message Sent',
869                    `Sent to ${tgt}: ${job.text.substring(0, 50)}${job.text.length > 50 ? '...' : ''}`,
870                    'success'
871                );
872                
873                panel.querySelector('#sch-text').value='';
874                searchInput.value = '';
875                selectedTarget = null;
876                refreshJobs();
877                alert('✅ Message sent successfully!');
878            } catch(err) {
879                const tgt = job.target.phone || job.target.name;
880                addToHistory('failed', tgt, job.text, err.message);
881                
882                showNotification(
883                    '❌ Message Failed',
884                    `Failed to send to ${tgt}: ${err.message}`,
885                    'error'
886                );
887                
888                refreshJobs();
889                alert('❌ Failed: ' + err.message);
890            }
891        };
892        
893        panel.querySelector('#btn-clear-form').onclick = ()=>{
894            panel.querySelector('#sch-text').value='';
895            searchInput.value = '';
896            selectedTarget = null;
897            panel.querySelector('#sch-when').value='';
898            prefillDate();
899        };
900        
901        panel.querySelector('#btn-refreshjobs').onclick = refreshJobs;
902
903        panel.querySelector('#book-save').onclick = ()=>{
904            const label = panel.querySelector('#book-label').value.trim();
905            const phone = panel.querySelector('#book-phone').value.trim();
906            if (!label || !phone) return alert('Label & phone required');
907            const book = loadBook();
908            const rec = { label, phone: normalizePhone(phone) };
909            const i = book.findIndex(x=>x.label.toLowerCase()===label.toLowerCase());
910            if (i>=0) book[i]=rec; else book.push(rec);
911            saveBook(book);
912            panel.querySelector('#book-label').value=''; panel.querySelector('#book-phone').value='';
913            renderBook();
914        };
915        
916        let parsedCSVData = [];
917        const csvFileInput = panel.querySelector('#csv-file');
918        const importPreview = panel.querySelector('#import-preview');
919        const importPreviewContent = panel.querySelector('#import-preview-content');
920        
921        panel.querySelector('#btn-import-csv').onclick = () => {
922            csvFileInput.click();
923        };
924        
925        csvFileInput.onchange = async (e) => {
926            const file = e.target.files[0];
927            if (!file) return;
928            
929            try {
930                const text = await file.text();
931                const lines = text.split('\n').filter(l => l.trim());
932                parsedCSVData = [];
933                
934                lines.forEach((line, idx) => {
935                    const parts = line.split(',').map(p => p.trim());
936                    if (parts.length < 3) return;
937                    
938                    const [target, message, datetime] = parts;
939                    const when = Date.parse(datetime);
940                    
941                    if (!target || !message || isNaN(when)) {
942                        log('Skipping invalid line', idx + 1, ':', line);
943                        return;
944                    }
945                    
946                    const digitsOnly = target.replace(/[^\d]/g, '');
947                    const isPhone = digitsOnly.length >= 8;
948                    
949                    parsedCSVData.push({
950                        target: isPhone ? { name: null, phone: normalizePhone(target) } : { name: target, phone: null },
951                        text: message,
952                        when: when
953                    });
954                });
955                
956                if (parsedCSVData.length === 0) {
957                    alert('No valid entries found in CSV');
958                    return;
959                }
960                
961                importPreviewContent.innerHTML = '';
962                parsedCSVData.forEach((item, idx) => {
963                    const tgt = item.target.phone ? `📞 ${item.target.phone}` : `👤 ${item.target.name}`;
964                    const previewItem = el('div', {style: 'padding:6px;background:rgba(255,255,255,.05);border-radius:4px;margin-bottom:4px'});
965                    previewItem.innerHTML = `
966                        <div style="font-weight:700;color:#3b82f6">${idx + 1}. ${tgt}</div>
967                        <div style="opacity:0.8;font-size:10px">${item.text.substring(0, 50)}${item.text.length > 50 ? '...' : ''}</div>
968                        <div style="opacity:0.6;font-size:10px">${fmt(item.when)}</div>
969                    `;
970                    importPreviewContent.appendChild(previewItem);
971                });
972                
973                importPreview.style.display = 'block';
974            } catch (err) {
975                alert('Failed to read CSV: ' + err.message);
976                log('CSV import error:', err);
977            }
978        };
979        
980        panel.querySelector('#btn-confirm-import').onclick = () => {
981            if (parsedCSVData.length === 0) return;
982            
983            const jobs = loadJobs();
984            parsedCSVData.forEach(item => {
985                jobs.push({
986                    id: uid(),
987                    when: item.when,
988                    text: item.text,
989                    target: item.target
990                });
991            });
992            saveJobs(jobs);
993            
994            alert(`✅ Imported ${parsedCSVData.length} messages successfully!`);
995            parsedCSVData = [];
996            importPreview.style.display = 'none';
997            csvFileInput.value = '';
998            refreshJobs();
999        };
1000        
1001        panel.querySelector('#btn-cancel-import').onclick = () => {
1002            parsedCSVData = [];
1003            importPreview.style.display = 'none';
1004            csvFileInput.value = '';
1005        };
1006        
1007        // Export by Date Range functionality
1008        const exportModal = panel.querySelector('#export-modal');
1009        const closeExportModal = panel.querySelector('#close-export-modal');
1010        const exportStartDate = panel.querySelector('#export-start-date');
1011        const exportEndDate = panel.querySelector('#export-end-date');
1012        const exportIncludeHistory = panel.querySelector('#export-include-history');
1013        const exportIncludeScheduled = panel.querySelector('#export-include-scheduled');
1014        const exportIncludeArchive = panel.querySelector('#export-include-archive');
1015        const exportPreviewSection = panel.querySelector('#export-preview-section');
1016        const exportPreviewText = panel.querySelector('#export-preview-text');
1017        
1018        // Set default dates (last 30 days to today)
1019        const today = new Date();
1020        const thirtyDaysAgo = new Date(today);
1021        thirtyDaysAgo.setDate(today.getDate() - 30);
1022        exportStartDate.value = thirtyDaysAgo.toISOString().split('T')[0];
1023        exportEndDate.value = today.toISOString().split('T')[0];
1024        
1025        panel.querySelector('#btn-export-range').onclick = () => {
1026            exportModal.style.display = 'flex';
1027            updateExportPreview();
1028        };
1029        
1030        closeExportModal.onclick = () => {
1031            exportModal.style.display = 'none';
1032        };
1033        
1034        exportModal.onclick = (e) => {
1035            if (e.target === exportModal) exportModal.style.display = 'none';
1036        };
1037        
1038        // Update preview when dates or checkboxes change
1039        exportStartDate.onchange = updateExportPreview;
1040        exportEndDate.onchange = updateExportPreview;
1041        exportIncludeHistory.onchange = updateExportPreview;
1042        exportIncludeScheduled.onchange = updateExportPreview;
1043        exportIncludeArchive.onchange = updateExportPreview;
1044        
1045        function updateExportPreview() {
1046            const data = getExportData();
1047            if (data.length === 0) {
1048                exportPreviewSection.style.display = 'none';
1049            } else {
1050                exportPreviewSection.style.display = 'block';
1051                const historyCount = data.filter(d => d.type === 'history').length;
1052                const scheduledCount = data.filter(d => d.type === 'scheduled').length;
1053                const archivedCount = data.filter(d => d.type === 'archive').length;
1054                exportPreviewText.innerHTML = `
1055                    Found <strong>${data.length}</strong> total messages:<br>
1056<span style="color:#25D366">${historyCount}</span> history items (sent/failed)<br>
1057<span style="color:#3b82f6">${scheduledCount}</span> scheduled messages<br>
1058<span style="color:#f59e0b">${archivedCount}</span> archived items
1059                `;
1060            }
1061        }
1062        
1063        function getExportData() {
1064            const startDate = new Date(exportStartDate.value);
1065            startDate.setHours(0, 0, 0, 0);
1066            const endDate = new Date(exportEndDate.value);
1067            endDate.setHours(23, 59, 59, 999);
1068            
1069            const includeHistory = exportIncludeHistory.checked;
1070            const includeScheduled = exportIncludeScheduled.checked;
1071            const includeArchive = exportIncludeArchive.checked;
1072            
1073            const data = [];
1074            
1075            if (includeHistory) {
1076                const history = loadHistory();
1077                history.forEach(h => {
1078                    const itemDate = new Date(h.timestamp);
1079                    if (itemDate >= startDate && itemDate <= endDate) {
1080                        data.push({
1081                            type: 'history',
1082                            status: h.status,
1083                            target: h.target,
1084                            message: h.text,
1085                            timestamp: h.timestamp,
1086                            date: fmt(h.timestamp),
1087                            error: h.error || ''
1088                        });
1089                    }
1090                });
1091            }
1092            
1093            if (includeScheduled) {
1094                const jobs = loadJobs();
1095                jobs.forEach(j => {
1096                    const itemDate = new Date(j.when);
1097                    if (itemDate >= startDate && itemDate <= endDate) {
1098                        const target = j.target.phone ? j.target.phone : j.target.name;
1099                        data.push({
1100                            type: 'scheduled',
1101                            status: 'pending',
1102                            target: target,
1103                            message: j.text,
1104                            timestamp: j.when,
1105                            date: fmt(j.when),
1106                            repeat: j.repeat || '',
1107                            weeklyDays: j.weeklyDays ? j.weeklyDays.join(',') : ''
1108                        });
1109                    }
1110                });
1111            }
1112            
1113            if (includeArchive) {
1114                const archive = loadArchive();
1115                archive.forEach(a => {
1116                    const itemDate = new Date(a.timestamp);
1117                    if (itemDate >= startDate && itemDate <= endDate) {
1118                        data.push({
1119                            type: 'archive',
1120                            status: a.status,
1121                            target: a.target,
1122                            message: a.text,
1123                            timestamp: a.timestamp,
1124                            date: fmt(a.timestamp),
1125                            error: a.error || '',
1126                            repeat: a.repeat || '',
1127                            weeklyDays: a.weeklyDays || '',
1128                            archivedAt: a.archivedAt ? fmt(a.archivedAt) : ''
1129                        });
1130                    }
1131                });
1132            }
1133            
1134            // Sort by timestamp
1135            data.sort((a, b) => a.timestamp - b.timestamp);
1136            
1137            return data;
1138        }
1139        
1140        panel.querySelector('#btn-export-json').onclick = () => {
1141            const data = getExportData();
1142            if (data.length === 0) {
1143                alert('No data to export in the selected date range');
1144                return;
1145            }
1146            
1147            const json = JSON.stringify(data, null, 2);
1148            const blob = new Blob([json], { type: 'application/json' });
1149            const url = URL.createObjectURL(blob);
1150            const a = document.createElement('a');
1151            a.href = url;
1152            a.download = `whatsapp-messages-${exportStartDate.value}-to-${exportEndDate.value}.json`;
1153            document.body.appendChild(a);
1154            a.click();
1155            document.body.removeChild(a);
1156            URL.revokeObjectURL(url);
1157            
1158            showNotification(
1159                '✅ Export Successful',
1160                `Exported ${data.length} messages to JSON`,
1161                'success'
1162            );
1163        };
1164        
1165        panel.querySelector('#btn-export-csv').onclick = () => {
1166            const data = getExportData();
1167            if (data.length === 0) {
1168                alert('No data to export in the selected date range');
1169                return;
1170            }
1171            
1172            // CSV header
1173            let csv = 'Type,Status,Target,Message,Date,Timestamp,Error,Repeat,WeeklyDays,ArchivedAt\n';
1174            
1175            // CSV rows
1176            data.forEach(item => {
1177                const row = [
1178                    item.type,
1179                    item.status,
1180                    `"${item.target.replace(/"/g, '""')}"`,
1181                    `"${item.message.replace(/"/g, '""')}"`,
1182                    `"${item.date}"`,
1183                    item.timestamp,
1184                    `"${(item.error || '').replace(/"/g, '""')}"`,
1185                    item.repeat || '',
1186                    item.weeklyDays || '',
1187                    item.archivedAt || ''
1188                ];
1189                csv += row.join(',') + '\n';
1190            });
1191            
1192            const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
1193            const url = URL.createObjectURL(blob);
1194            const a = document.createElement('a');
1195            a.href = url;
1196            a.download = `whatsapp-messages-${exportStartDate.value}-to-${exportEndDate.value}.csv`;
1197            document.body.appendChild(a);
1198            a.click();
1199            document.body.removeChild(a);
1200            URL.revokeObjectURL(url);
1201            
1202            showNotification(
1203                '✅ Export Successful',
1204                `Exported ${data.length} messages to CSV`,
1205                'success'
1206            );
1207        };
1208
1209        function jobRow(job){
1210            const tgt = job.target.phone ? `Phone: ${job.target.phone}` : `Name: ${job.target.name}`;
1211            const row = el('div',{class:'waw-item'});
1212            row.innerHTML = `
1213        <div style="flex:1">
1214          <div style="font-weight:700;font-size:13px">${fmt(job.when)}</div>
1215          <div class="waw-small countdown-${job.id}" style="color:#25D366;font-weight:600;margin:4px 0">${timeUntil(job.when)}</div>
1216          <div class="waw-small" style="opacity:0.8">${tgt}</div>
1217          <div class="waw-small" style="white-space:pre-wrap;max-width:240px;margin-top:6px;background:rgba(255,255,255,.05);padding:8px;border-radius:8px;border-left:3px solid #25D366">${job.text}</div>
1218        </div>
1219        <div style="display:flex;flex-direction:column;gap:6px;min-width:90px">
1220          <button class="waw-btn secondary" data-act="edit" data-id="${job.id}" style="padding:8px 12px;font-size:11px">✏️ Edit</button>
1221          <button class="waw-btn secondary" data-act="sendnow" data-id="${job.id}" style="padding:8px 12px;font-size:11px">🚀 Send</button>
1222          <button class="waw-btn secondary" data-act="del" data-id="${job.id}" style="padding:8px 12px;font-size:11px;background:rgba(220,38,38,.2);border-color:rgba(220,38,38,.3)">🗑️ Del</button>
1223        </div>`;
1224            return row;
1225        }
1226        
1227        function refreshJobs(){
1228            const box = panel.querySelector('#jobs-list');
1229            const jobs = loadJobs().sort((a,b)=>a.when-b.when);
1230            box.innerHTML = jobs.length ? '' : '<div class="waw-small" style="text-align:center;padding:20px;opacity:0.5">No scheduled messages yet 📭</div>';
1231            jobs.forEach(j=>box.appendChild(jobRow(j)));
1232            
1233            const fab = document.querySelector('.waw-fab');
1234            if (jobs.length > 0) {
1235                fab.classList.add('has-jobs');
1236                fab.innerHTML = jobs.length;
1237            } else {
1238                fab.classList.remove('has-jobs');
1239                fab.innerHTML = '⏰';
1240            }
1241            
1242            updateStats();
1243            
1244            box.onclick = async (e)=>{
1245                const b = e.target.closest('button'); if (!b) return;
1246                const act=b.getAttribute('data-act'); const id=b.getAttribute('data-id');
1247                const jobs = loadJobs(); const job = jobs.find(x=>x.id===id); if (!job) return;
1248                if (act==='del'){ 
1249                    if (confirm('Delete this scheduled message?')) {
1250                        archiveJob(job);
1251                        saveJobs(jobs.filter(x=>x.id!==id)); 
1252                        refreshJobs(); 
1253                    }
1254                }
1255                if (act==='edit'){
1256                    panel.querySelector('#sch-text').value = job.text;
1257                    const dt = panel.querySelector('#sch-when');
1258                    const t = new Date(job.when);
1259                    const year = t.getFullYear();
1260                    const month = pad(t.getMonth() + 1);
1261                    const day = pad(t.getDate());
1262                    const hours = pad(t.getHours());
1263                    const minutes = pad(t.getMinutes());
1264                    dt.value = `${year}-${month}-${day}T${hours}:${minutes}`;
1265                    
1266                    if (job.target.phone) {
1267                        searchInput.value = '📞 ' + job.target.phone;
1268                        selectedTarget = { type: 'phone', label: job.target.phone, value: job.target.phone, isPhone: true };
1269                    } else if (job.target.name) {
1270                        searchInput.value = job.target.name;
1271                        selectedTarget = { type: 'chat', label: job.target.name, value: job.target.name };
1272                    }
1273                    
1274                    saveJobs(jobs.filter(x=>x.id!==id));
1275                    refreshJobs();
1276                    
1277                    panel.querySelector('.waw-content').scrollTop = 0;
1278                }
1279                if (act==='sendnow'){
1280                    if (confirm('Send this message now?')) {
1281                        try { 
1282                            await performJob(job); 
1283                            const tgt = job.target.phone || job.target.name;
1284                            addToHistory('sent', tgt, job.text);
1285                            
1286                            showNotification(
1287                                '✅ Message Sent',
1288                                `Sent to ${tgt}: ${job.text.substring(0, 50)}${job.text.length > 50 ? '...' : ''}`,
1289                                'success'
1290                            );
1291                            
1292                            saveJobs(loadJobs().filter(x=>x.id!==id)); 
1293                            refreshJobs(); 
1294                            alert('✅ Message sent successfully!'); 
1295                        }
1296                        catch(err){ 
1297                            const tgt = job.target.phone || job.target.name;
1298                            addToHistory('failed', tgt, job.text, err.message);
1299                            
1300                            showNotification(
1301                                '❌ Message Failed',
1302                                `Failed to send to ${tgt}: ${err.message}`,
1303                                'error'
1304                            );
1305                            
1306                            refreshJobs();
1307                            alert('❌ Failed: '+err.message); 
1308                        }
1309                    }
1310                }
1311            };
1312        }
1313        
1314        function updateStats(){
1315            const stats = getStats();
1316            const sentEl = panel.querySelector('#stat-sent');
1317            const failedEl = panel.querySelector('#stat-failed');
1318            const pendingEl = panel.querySelector('#stat-pending');
1319            
1320            if (sentEl) sentEl.textContent = stats.sentToday;
1321            if (failedEl) failedEl.textContent = stats.failedToday;
1322            if (pendingEl) pendingEl.textContent = stats.pendingTotal;
1323            
1324            const recentList = panel.querySelector('#recent-sent-list');
1325            const recentSection = panel.querySelector('#recent-sent-section');
1326            if (recentList && stats.recentSent.length > 0) {
1327                recentSection.style.display = 'block';
1328                recentList.innerHTML = '';
1329                stats.recentSent.forEach(h => {
1330                    const item = el('div', {class: 'waw-small', style: 'padding:8px;background:rgba(37,211,102,.08);border-radius:8px;margin-bottom:6px;border-left:3px solid #25D366;display:flex;justify-content:space-between;align-items:center;gap:8px'});
1331                    item.innerHTML = `
1332                        <div style="flex:1">
1333                            <div style="display:flex;justify-content:space-between;margin-bottom:4px">
1334                                <span style="font-weight:700;color:#25D366">${h.target}</span>
1335                                <span style="opacity:0.7">${fmtShort(h.timestamp)}</span>
1336                            </div>
1337                            <div style="opacity:0.8">${h.text}</div>
1338                        </div>
1339                        <button class="waw-btn secondary" data-act="dismiss" data-id="${h.id}" style="padding:6px 10px;font-size:11px;min-width:auto;white-space:nowrap" title="Mark as seen">✓ Seen</button>
1340                    `;
1341                    recentList.appendChild(item);
1342                });
1343                
1344                recentList.onclick = (e) => {
1345                    const btn = e.target.closest('button[data-act="dismiss"]');
1346                    if (!btn) return;
1347                    const id = btn.getAttribute('data-id');
1348                    const history = loadHistory();
1349                    const item = history.find(h => h.id === id);
1350                    if (item) {
1351                        const archive = loadArchive();
1352                        archive.push({
1353                            ...item,
1354                            archivedAt: Date.now(),
1355                            dismissedAt: Date.now()
1356                        });
1357                        saveArchive(archive);
1358                    }
1359                    const filtered = history.filter(h => h.id !== id);
1360                    saveHistory(filtered);
1361                    updateStats();
1362                };
1363            } else if (recentSection) {
1364                recentSection.style.display = 'none';
1365            }
1366        }
1367        
1368        function renderBook(){
1369            const list = panel.querySelector('#book-list');
1370            const book = loadBook().sort((a,b)=>a.label.localeCompare(b.label));
1371            list.innerHTML = book.length ? '' : '<div class="waw-small">Empty</div>';
1372            book.forEach(rec=>{
1373                const row = el('div',{class:'waw-item'});
1374                row.innerHTML = `
1375          <div>
1376            <div style="font-weight:700">${rec.label}</div>
1377            <div class="waw-small">${rec.phone}</div>
1378          </div>
1379          <div>
1380            <button class="waw-btn secondary" data-act="open" data-phone="${rec.phone}">Open</button>
1381            <button class="waw-btn secondary" data-act="use" data-phone="${rec.phone}">Use in scheduler</button>
1382            <button class="waw-btn secondary" data-act="del" data-label="${rec.label}">Delete</button>
1383          </div>`;
1384                list.appendChild(row);
1385            });
1386            list.onclick = async (e)=>{
1387                const b = e.target.closest('button'); if (!b) return;
1388                const act = b.getAttribute('data-act');
1389                if (act==='open'){
1390                    const phone = b.getAttribute('data-phone');
1391                    try { await openChatByPhone(phone); alert('Opened'); } catch(err){ alert('Failed: '+err.message); }
1392                } else if (act==='use'){
1393                    const phone = b.getAttribute('data-phone');
1394                    panel.querySelector('[data-tab="schedule"]').click();
1395                    panel.querySelector('#sch-name').value=''; panel.querySelector('#sch-phone').value=phone;
1396                } else if (act==='del'){
1397                    const label = b.getAttribute('data-label');
1398                    saveBook(loadBook().filter(x=>x.label!==label)); renderBook();
1399                }
1400            };
1401        }
1402        
1403        function prefillDate(){
1404            const dt = panel.querySelector('#sch-when');
1405            if (!dt.value){
1406                const t = new Date(Date.now() + 10*60*1000);
1407                const year = t.getFullYear();
1408                const month = pad(t.getMonth() + 1);
1409                const day = pad(t.getDate());
1410                const hours = pad(t.getHours());
1411                const minutes = pad(t.getMinutes());
1412                dt.value = `${year}-${month}-${day}T${hours}:${minutes}`;
1413            }
1414        }
1415
1416        return { refreshJobs, renderBook, prefillDate };
1417    }
1418
1419    async function performJob(job){
1420        const { name, phone } = job.target || {};
1421        
1422        if (phone){ 
1423            log('Saving pending message before navigation');
1424            save(LS.PENDING, { text: job.text, jobId: job.id, timestamp: Date.now() });
1425            await openChatByPhone(phone, ''); 
1426            await delay(900); 
1427        } else if (name){ 
1428            await openChatByName(name, { exact:false }); 
1429            await delay(700); 
1430        } else {
1431            throw new Error('No target');
1432        }
1433
1434        await sendMessage(job.text, { retries:3 });
1435    }
1436
1437    async function tick(){
1438        const now = Date.now();
1439        const jobs = loadJobs();
1440        log('Tick running, checking jobs. Current time:', now, 'Jobs:', jobs.length);
1441        const due = jobs.filter(j=>j.when<=now);
1442        if (!due.length) return;
1443        log('Found', due.length, 'messages ready to send');
1444        
1445        if (document.visibilityState === 'hidden') {
1446            log('Tab is hidden, bringing to front');
1447            window.focus();
1448            await delay(500);
1449        }
1450        
1451        for (const j of due){
1452            const tgt = j.target.phone || j.target.name;
1453            try {
1454                log('Sending job:', j.id, 'to', j.target);
1455                await performJob(j);
1456                addToHistory('sent', tgt, j.text);
1457                
1458                if (j.repeat) {
1459                    const nextTime = getNextOccurrence(j.when, j.repeat, j.weeklyDays);
1460                    if (nextTime) {
1461                        const nextJob = {
1462                            id: uid(),
1463                            when: nextTime,
1464                            text: j.text,
1465                            target: j.target,
1466                            repeat: j.repeat,
1467                            weeklyDays: j.weeklyDays
1468                        };
1469                        const currentJobs = loadJobs().filter(x => x.id !== j.id);
1470                        currentJobs.push(nextJob);
1471                        saveJobs(currentJobs);
1472                        log('Created next occurrence for recurring job:', nextJob.id, 'at', fmt(nextTime));
1473                    }
1474                } else {
1475                    saveJobs(loadJobs().filter(x=>x.id!==j.id));
1476                }
1477                
1478                log('Job sent successfully:', j.id);
1479                await delay(900);
1480            } catch (e) {
1481                log('Job failed', j, e);
1482                addToHistory('failed', tgt, j.text, e.message);
1483            }
1484        }
1485    }
1486    
1487    function startLoop(){ setInterval(tick, 5000); }
1488    
1489    function updateCountdowns(){
1490        const jobs = loadJobs();
1491        jobs.forEach(job => {
1492            const el = document.querySelector(`.countdown-${job.id}`);
1493            if (el) el.textContent = timeUntil(job.when);
1494        });
1495    }
1496
1497    let wakeLock;
1498    async function requestWakeLock(){
1499        try { if ('wakeLock' in navigator) wakeLock = await navigator.wakeLock.request('screen'); }
1500        catch (e) { log('WakeLock error', e); }
1501    }
1502    function releaseWakeLock(){ try { wakeLock && wakeLock.release(); wakeLock = null; } catch {} }
1503
1504    async function init(){
1505        log('Initializing WhatsApp Pro Scheduler v3.3');
1506        injectStyles();
1507        const panelAPI = createPanel();
1508        try { await waitFor('#app', { timeout:60000 }); } catch {}
1509        
1510        const fabBtn = document.querySelector('.waw-fab');
1511        const panelEl = document.querySelector('.waw-panel');
1512        const origFabClick = fabBtn.onclick;
1513        fabBtn.onclick = async () => {
1514            origFabClick();
1515            if (panelEl.style.display !== 'none') { await requestWakeLock(); }
1516            else { releaseWakeLock(); }
1517        };
1518        window.addEventListener('visibilitychange', ()=> {
1519            if (document.visibilityState === 'visible' && panelEl.style.display !== 'none') requestWakeLock();
1520        });
1521        
1522        const pending = load(LS.PENDING, null);
1523        if (pending && pending.text && (Date.now() - pending.timestamp < 120000)) {
1524            log('Found pending message, sending:', pending.text);
1525            try {
1526                await waitFor(selectors.composer.join(','), { timeout:30000 });
1527                await delay(1000);
1528                await sendMessage(pending.text, { retries:3 });
1529                log('Pending message sent successfully');
1530                localStorage.removeItem(LS.PENDING);
1531                if (pending.jobId) {
1532                    const jobs = loadJobs().filter(x => x.id !== pending.jobId);
1533                    saveJobs(jobs);
1534                }
1535                const chatTitle = getActiveChatTitle();
1536                const target = chatTitle || pending.target || 'Unknown';
1537                addToHistory('sent', target, pending.text);
1538                log('Added to history:', target);
1539            } catch (e) {
1540                log('Failed to send pending message:', e);
1541                const chatTitle = getActiveChatTitle();
1542                const target = chatTitle || pending.target || 'Unknown';
1543                addToHistory('failed', target, pending.text, e.message);
1544            }
1545        }
1546        
1547        startLoop();
1548        setInterval(updateCountdowns, 1000);
1549        const prefs = loadPrefs(); 
1550        if (prefs.panelOpen){ 
1551            document.querySelector('.waw-fab').click(); 
1552        }
1553    }
1554
1555    if (document.readyState==='loading') document.addEventListener('DOMContentLoaded', init);
1556    else init();
1557
1558})();
WhatsApp Message Scheduler Pro | Robomonkey