Track product prices (Amazon/eBay/AliExpress/Booking). Background checks. WhatsApp alert on drop.
Size
116.0 KB
Version
1.3.61
Created
Nov 9, 2025
Updated
20 days ago
1// ==UserScript==
2// @name Price Tracker Pro — WhatsApp Alerts
3// @description Track product prices (Amazon/eBay/AliExpress/Booking). Background checks. WhatsApp alert on drop.
4// @version 1.3.61
5// @match https://web.whatsapp.com/*
6// @match https://*/*
7// @match http://*/*
8// @icon https://www.svgrepo.com/show/331488/price-tag.svg
9// @grant GM.xmlHttpRequest
10// @grant GM.getValue
11// @grant GM.setValue
12// @grant GM.notification
13// @grant GM.openInTab
14// @grant GM.setClipboard
15// @namespace io.robomonkey.price.tracker.pro
16// @author You
17// @connect *
18// ==/UserScript==
19(function() {
20 'use strict';
21
22 // Prevent multiple initializations
23 if (window.stockTrackerInitialized) {
24 console.log('Stock Tracker already initialized');
25 return;
26 }
27 window.stockTrackerInitialized = true;
28
29 const STORAGE_KEY = 'stock_tracker_items';
30 const PREFS_KEY = 'stock_tracker_prefs';
31
32 // Blocked domains - sites where the extension should NOT run
33 const BLOCKED_DOMAINS = [
34 'google.com',
35 'gmail.com',
36 'youtube.com',
37 'facebook.com',
38 'twitter.com',
39 'x.com',
40 'instagram.com',
41 'linkedin.com',
42 'reddit.com',
43 'wikipedia.org',
44 'github.com',
45 'stackoverflow.com',
46 'openai.com',
47 'chatgpt.com',
48 'chat.openai.com',
49 'claude.ai',
50 'anthropic.com',
51 'bing.com',
52 'yahoo.com',
53 'docs.google.com',
54 'drive.google.com',
55 'dropbox.com',
56 'notion.so',
57 'slack.com',
58 'discord.com',
59 'telegram.org',
60 'messenger.com',
61 'zoom.us',
62 'meet.google.com',
63 'teams.microsoft.com',
64 'office.com',
65 'microsoft.com',
66 'apple.com',
67 'icloud.com',
68 'netflix.com',
69 'spotify.com',
70 'twitch.tv',
71 'tiktok.com',
72 'pinterest.com',
73 'tumblr.com',
74 'medium.com',
75 'wordpress.com',
76 'blogger.com',
77 'vimeo.com',
78 'dailymotion.com',
79 'soundcloud.com',
80 'news.ycombinator.com',
81 'producthunt.com'
82 ];
83
84 // Check if current site is blocked
85 function isBlockedSite() {
86 const host = location.host.toLowerCase();
87 return BLOCKED_DOMAINS.some(domain => host.includes(domain));
88 }
89
90 // Check if site looks like an e-commerce site
91 function isEcommerceSite() {
92 // Check for common e-commerce indicators in the page
93 const indicators = [
94 // Price indicators
95 '[itemprop="price"]',
96 '[class*="price"]',
97 '[data-price]',
98 '[id*="price"]',
99 // Product indicators
100 '[itemprop="product"]',
101 '[class*="product"]',
102 '[data-product]',
103 // Cart indicators
104 '[class*="cart"]',
105 '[id*="cart"]',
106 '[class*="basket"]',
107 // Buy/Add to cart buttons
108 'button[class*="add-to-cart"]',
109 'button[class*="buy"]',
110 'button[id*="add-to-cart"]',
111 // Size selectors
112 '[class*="size"]',
113 'select[name*="size"]',
114 // Common e-commerce meta tags
115 'meta[property="og:type"][content*="product"]',
116 'meta[name="product"]'
117 ];
118
119 // Check if any indicators exist
120 for (const selector of indicators) {
121 if (document.querySelector(selector)) {
122 return true;
123 }
124 }
125
126 // Check URL patterns
127 const url = location.href.toLowerCase();
128 const ecommercePatterns = [
129 '/product/',
130 '/item/',
131 '/p/',
132 '/dp/',
133 '/pd/',
134 'product-',
135 'item-',
136 '/shop/',
137 '/store/'
138 ];
139
140 return ecommercePatterns.some(pattern => url.includes(pattern));
141 }
142
143 // Storage helpers
144 const load = async (key, defaultValue) => {
145 try {
146 const val = await GM.getValue(key);
147 return val !== undefined ? JSON.parse(val) : defaultValue;
148 } catch(err) {
149 console.error('[Stock Tracker] Load error:', err);
150 return defaultValue;
151 }
152 };
153
154 const save = async (key, value) => {
155 try {
156 await GM.setValue(key, JSON.stringify(value));
157 } catch(err) {
158 console.error('[Stock Tracker] Save error:', err);
159 }
160 };
161
162 const getItems = () => load(STORAGE_KEY, []);
163 const saveItems = (items) => save(STORAGE_KEY, items);
164 const getPrefs = () => load(PREFS_KEY, { waPhone: '', checkInterval: 5, notifications: true, soundAlerts: true, autoAddToCart: false });
165 const savePrefs = (prefs) => save(PREFS_KEY, prefs);
166
167 // Countdown timer state
168 let nextCheckTime = null;
169 let countdownInterval = null;
170
171 // Time formatting helpers
172 function formatTimeAgo(timestamp) {
173 const now = Date.now();
174 const diff = now - timestamp;
175 const minutes = Math.floor(diff / 60000);
176 const hours = Math.floor(diff / 3600000);
177 const days = Math.floor(diff / 86400000);
178
179 if (minutes < 1) return 'עכשיו';
180 if (minutes < 60) return `לפני ${minutes} דקות`;
181 if (hours < 24) return `לפני ${hours} שעות`;
182 return `לפני ${days} ימים`;
183 }
184
185 // Parse price from text
186 function parsePrice(val) {
187 if (val == null) return null; // null/undefined
188 if (typeof val === 'number') return val; // כבר מספר
189 // כל דבר אחר – נהפוך למחרוזת בבטחה
190 let str = String(val);
191 // ננקה תווים שמטשטשים
192 str = str.replace(/[^\d.,\-]/g, '').trim();
193
194 // טיפול במבנים כמו "1,234.56" או "1.234,56"
195 // אם יש גם נקודות וגם פסיקים – נחליט ממי אלפים ומי עשרוניים
196 const hasDot = str.includes('.');
197 const hasComma = str.includes(',');
198 if (hasDot && hasComma) {
199 // אם הפסיק מופיע אחרי הנקודה – כנראה שהפסיק הוא אלפים ונשאיר נקודה כעשרוני
200 if (str.lastIndexOf(',') < str.lastIndexOf('.')) {
201 str = str.replace(/,/g, '');
202 } else {
203 // אחרת – הנקודה היא אלפים, נהפוך פסיק לעשרוני
204 str = str.replace(/\./g, '').replace(',', '.');
205 }
206 } else {
207 // רק פסיקים -> נניח שזה עשרוני אירופאי
208 if (hasComma && !hasDot) str = str.replace(',', '.');
209 // רק נקודות -> נשאיר כמו שהוא
210 }
211
212 const num = parseFloat(str);
213 return isNaN(num) ? null : num;
214 }
215
216 // Normalize size label for comparison
217 function normalizeSizeLabel(s) {
218 return String(s || '')
219 .toLowerCase()
220 .replace(/size|מידה|talla|größe|taglia|taille|尺码/gi, '')
221 .replace(/eu|uk|us|it|fr|de|men|women|unisex/gi, '')
222 .replace(/[()]/g, '')
223 .replace(/\s*\/\s*/g, ' ')
224 .replace(/\s+/g, ' ')
225 .trim();
226 }
227
228 // Generate size label variants for flexible matching
229 function labelVariants(s) {
230 const n = normalizeSizeLabel(s);
231 const toks = n.split(' ').filter(Boolean);
232
233 const variants = new Set([n]);
234 if (toks.length >= 2) {
235 variants.add(toks.find(t => /^[xsml]{1,3}$/.test(t)) || n);
236 const num = toks.find(t => /^\d{2,3}$/.test(t));
237 if (num) variants.add(num);
238 }
239 variants.add(n.replace(/\s+/g, ''));
240
241 return Array.from(variants).filter(Boolean);
242 }
243
244 // Check if two size labels match (flexible comparison)
245 function sizeMatches(jsonSize, savedLabel) {
246 const A = labelVariants(jsonSize);
247 const B = labelVariants(savedLabel);
248 return A.some(a => B.some(b => a === b || a.includes(b) || b.includes(a)));
249 }
250
251 // Sleep helper
252 function sleep(ms) {
253 return new Promise(resolve => setTimeout(resolve, ms));
254 }
255
256 // Queue auto-cart task
257 async function queueAutoCart(task) {
258 const key = 'st_autocart_queue';
259 const q = await load(key, []);
260 const now = Date.now();
261 const recent = q.filter(x => now - x.time < 30*60*1000);
262 const exists = recent.some(x => x.url === task.url && x.size === task.size);
263 if (!exists) {
264 recent.push({ ...task, time: now });
265 await save(key, recent);
266 const u = new URL(task.url);
267 u.searchParams.set('st_autocart', '1');
268 u.searchParams.set('st_size', encodeURIComponent(task.size));
269 u.searchParams.set('st_site', task.site);
270 GM.openInTab(u.toString(), { active: false, insert: true });
271 showToast(`🛒 מנסה להוסיף לסל: ${task.size}`, 'info');
272 }
273 }
274
275 // Auto-cart for Zara
276 async function autoCartZara(sizeLabel) {
277 console.log('[Stock Tracker] AutoCart Zara: Starting for size', sizeLabel);
278
279 // First, click the main add-to-cart button to open size selector
280 const addBtn = document.querySelector('button[data-qa-action="add-to-cart"]');
281 if (addBtn) {
282 console.log('[Stock Tracker] AutoCart Zara: Clicking add-to-cart button to open size selector');
283 addBtn.click();
284 await sleep(1500); // Wait longer for size selector to appear
285 } else {
286 console.warn('[Stock Tracker] AutoCart Zara: Add-to-cart button not found');
287 showToast('❌ לא נמצא כפתור הוספה לסל', 'error');
288 return;
289 }
290
291 // Find all size elements
292 const sizeElements = Array.from(document.querySelectorAll('li.size-selector-sizes__size'));
293 console.log('[Stock Tracker] AutoCart Zara: Found', sizeElements.length, 'size elements');
294
295 // Find the matching size
296 const targetSize = sizeElements.find(li => {
297 const labelEl = li.querySelector('.size-selector-sizes-size__label');
298 const label = labelEl?.textContent?.trim();
299 console.log('[Stock Tracker] AutoCart Zara: Checking size:', label);
300 return label && label === sizeLabel;
301 });
302
303 if (!targetSize) {
304 console.warn('[Stock Tracker] AutoCart Zara: Size not found:', sizeLabel);
305 showToast(`❌ מידה ${sizeLabel} לא נמצאה`, 'error');
306 return;
307 }
308
309 // Check if size is available (NOT disabled AND NOT unavailable)
310 const isDisabled = targetSize.classList.contains('size-selector-sizes__size--disabled');
311 const isUnavailable = targetSize.classList.contains('size-selector-sizes-size--unavailable');
312
313 if (isDisabled || isUnavailable) {
314 console.warn('[Stock Tracker] AutoCart Zara: Size is unavailable:', sizeLabel);
315 showToast(`❌ מידה ${sizeLabel} לא זמינה`, 'error');
316 return;
317 }
318
319 // Click the button inside the size element
320 const sizeButton = targetSize.querySelector('button.size-selector-sizes-size__button');
321 if (!sizeButton) {
322 console.warn('[Stock Tracker] AutoCart Zara: Size button not found');
323 showToast('❌ לא נמצא כפתור המידה', 'error');
324 return;
325 }
326
327 console.log('[Stock Tracker] AutoCart Zara: Clicking size button for:', sizeLabel);
328 sizeButton.scrollIntoView({behavior:'smooth', block:'center'});
329 await sleep(300);
330 sizeButton.click();
331 await sleep(800); // Wait for size selection to process
332
333 // Now find and click the final add-to-cart button
334 const finalAddBtn = document.querySelector('button[data-qa-action="add-to-cart"]');
335 if (finalAddBtn && finalAddBtn.textContent.includes('הוספה לסל')) {
336 console.log('[Stock Tracker] AutoCart Zara: Clicking final add-to-cart button');
337 finalAddBtn.click();
338 await sleep(1500); // Wait for cart update
339
340 // Check if item was added to cart
341 const cartBadge = document.querySelector('[data-qa="header-cart"] [data-qa*="counter"], [data-qa="cart-counter"]');
342 if (cartBadge) {
343 console.log('[Stock Tracker] AutoCart Zara: Cart counter now =', cartBadge.textContent);
344 }
345
346 showToast(`✅ מידה ${sizeLabel} נוספה לסל!`, 'success');
347 } else {
348 console.warn('[Stock Tracker] AutoCart Zara: Final add-to-cart button not found or not ready');
349 showToast('⚠️ לא הצלחתי להוסיף לסל', 'error');
350 }
351 }
352
353 // Auto-cart for Bershka
354 async function autoCartBershka(sizeLabel) {
355 console.log('[Stock Tracker] AutoCart Bershka: Starting for size', sizeLabel);
356 await sleep(600);
357
358 const sizeButtons = Array.from(document.querySelectorAll('button[data-qa-anchor="sizeListItem"], .ui--dot-item, button[aria-label*="מידה"], button[aria-label*="Size"]'));
359 console.log('[Stock Tracker] AutoCart Bershka: Found', sizeButtons.length, 'size buttons');
360
361 const btn = sizeButtons.find(b => {
362 const raw = (b.querySelector('.text__label, .size__name, [data-qa="size-name"]')?.textContent || b.getAttribute('aria-label') || b.textContent || '').trim();
363 return sizeMatches(raw.replace(/^מידה\s*/,'').trim(), sizeLabel);
364 });
365
366 if (!btn) {
367 console.warn('[Stock Tracker] AutoCart Bershka: Size not found', sizeLabel);
368 return;
369 }
370
371 if (btn.disabled || btn.getAttribute('aria-disabled') === 'true' || btn.classList.contains('is-unavailable')) {
372 console.warn('[Stock Tracker] AutoCart Bershka: Size appears unavailable in UI');
373 return;
374 }
375
376 console.log('[Stock Tracker] AutoCart Bershka: Clicking size button');
377 btn.scrollIntoView({behavior:'smooth', block:'center'});
378 btn.click();
379 await sleep(300);
380
381 const addBtn = document.querySelector('button[data-qa="add-to-cart"], button[aria-label*="הוסף"], button[aria-label*="Add"]');
382 if (addBtn) {
383 console.log('[Stock Tracker] AutoCart Bershka: Clicking add-to-cart button');
384 addBtn.click();
385 await sleep(1200);
386 }
387
388 const miniCart = document.querySelector('[data-qa="minicart"]') || document.querySelector('[class*="minicart"]');
389 if (miniCart) {
390 console.log('[Stock Tracker] AutoCart Bershka: Mini cart opened');
391 }
392
393 showToast(`🛒 נוספה לסל ב-Bershka: ${sizeLabel}`, 'success');
394 }
395
396 // Auto-cart generic fallback
397 async function autoCartGeneric(sizeLabel) {
398 console.log('[Stock Tracker] AutoCart Generic: Starting for size', sizeLabel);
399
400 const candidates = Array.from(document.querySelectorAll(
401 'button[class*="size"], [role="button"][class*="size"], [data-size], select[name*="size"], select[id*="size"], button[aria-label*="Size"], button[aria-label*="מידה"]'
402 ));
403
404 const select = candidates.find(el => el.tagName === 'SELECT');
405 if (select) {
406 const opt = Array.from(select.options).find(o => sizeMatches((o.textContent||'').trim(), sizeLabel));
407 if (opt) {
408 console.log('[Stock Tracker] AutoCart Generic: Selecting size from dropdown');
409 select.value = opt.value;
410 select.dispatchEvent(new Event('change', {bubbles:true}));
411 await sleep(300);
412 }
413 } else {
414 const btn = candidates.find(el => sizeMatches((el.textContent||el.getAttribute('data-size')||el.getAttribute('aria-label')||'').trim(), sizeLabel));
415 if (btn && !btn.disabled) {
416 console.log('[Stock Tracker] AutoCart Generic: Clicking size button');
417 btn.click();
418 await sleep(300);
419 }
420 }
421
422 const addBtn = document.querySelector('button[id*="add-to-cart"], button[class*="add-to-cart"], [aria-label*="Add to cart"], [aria-label*="הוסף לסל"]');
423 if (addBtn) {
424 console.log('[Stock Tracker] AutoCart Generic: Clicking add-to-cart button');
425 addBtn.click();
426 await sleep(1200);
427 }
428
429 showToast(`🛒 נוספה לסל: ${sizeLabel}`, 'success');
430 }
431
432 // Get URL parameter
433 function getParam(name) {
434 try {
435 return new URL(location.href).searchParams.get(name);
436 } catch {
437 return null;
438 }
439 }
440
441 // Run auto-cart if requested via URL parameters
442 async function runAutoCartIfRequested() {
443 const flag = getParam('st_autocart');
444 const wanted = getParam('st_size') ? decodeURIComponent(getParam('st_size')) : null;
445 if (!flag || !wanted) return;
446
447 console.log('[Stock Tracker] AutoCart requested for size:', wanted);
448 await sleep(1000);
449
450 const host = location.host.replace('www.','');
451 try {
452 if (host.includes('zara.com')) {
453 await autoCartZara(wanted);
454 } else if (host.includes('bershka.com')) {
455 await autoCartBershka(wanted);
456 } else {
457 await autoCartGeneric(wanted);
458 }
459 } catch(e){
460 console.warn('[Stock Tracker] AutoCart error:', e);
461 }
462 }
463
464 // Extract site name from URL
465 function getSiteName(url) {
466 try {
467 const hostname = new URL(url).hostname;
468 // Remove www. and get domain name
469 const domain = hostname.replace(/^www\d?\./, '');
470
471 // Map of known sites to friendly names
472 const siteNames = {
473 'zara.com': 'Zara',
474 'bershka.com': 'Bershka',
475 'hm.com': 'H&M',
476 'nike.com': 'Nike',
477 'amazon.com': 'Amazon',
478 'amazon.co.uk': 'Amazon UK',
479 'amazon.de': 'Amazon DE',
480 'ebay.com': 'eBay',
481 'aliexpress.com': 'AliExpress',
482 'asos.com': 'ASOS',
483 'shein.com': 'SHEIN',
484 'mango.com': 'Mango',
485 'pullandbear.com': 'Pull&Bear',
486 'stradivarius.com': 'Stradivarius',
487 'massimodutti.com': 'Massimo Dutti',
488 'gap.com': 'Gap',
489 'uniqlo.com': 'Uniqlo',
490 'adidas.com': 'Adidas',
491 'puma.com': 'Puma',
492 'reebok.com': 'Reebok',
493 'newbalance.com': 'New Balance',
494 'underarmour.com': 'Under Armour'
495 };
496
497 // Check if we have a friendly name
498 for (const [key, name] of Object.entries(siteNames)) {
499 if (domain.includes(key)) {
500 return name;
501 }
502 }
503
504 // Otherwise, capitalize first letter of domain
505 const mainDomain = domain.split('.')[0];
506 return mainDomain.charAt(0).toUpperCase() + mainDomain.slice(1);
507 } catch {
508 return 'Unknown';
509 }
510 }
511
512 // Play notification sound
513 function playNotificationSound() {
514 try {
515 const audioContext = new (window.AudioContext || window.webkitAudioContext)();
516 const oscillator = audioContext.createOscillator();
517 const gainNode = audioContext.createGain();
518
519 oscillator.connect(gainNode);
520 gainNode.connect(audioContext.destination);
521
522 oscillator.frequency.value = 800;
523 oscillator.type = 'sine';
524
525 gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
526 gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
527
528 oscillator.start(audioContext.currentTime);
529 oscillator.stop(audioContext.currentTime + 0.5);
530 } catch (err) {
531 console.error('[Stock Tracker] Sound error:', err);
532 }
533 }
534
535 // Update FAB badge
536 async function updateFabBadge() {
537 const items = await getItems();
538 const fab = document.querySelector('.st-fab');
539 if (!fab) return;
540
541 const outOfStockCount = items.reduce((sum, item) =>
542 sum + item.sizes.filter(s => !s.inStock).length, 0
543 );
544
545 // Remove old badge
546 const oldBadge = fab.querySelector('.st-badge');
547 if (oldBadge) oldBadge.remove();
548
549 // Add new badge if there are out of stock items
550 if (outOfStockCount > 0) {
551 const badge = document.createElement('span');
552 badge.className = 'st-badge';
553 badge.textContent = outOfStockCount;
554 fab.appendChild(badge);
555
556 // Add pulse animation if items are out of stock
557 fab.classList.add('st-fab-alert');
558 } else {
559 fab.classList.remove('st-fab-alert');
560 }
561 }
562
563 // Universal site adapters
564 const adapters = {
565 // Zara
566 'zara.com': {
567 title: () => {
568 const titleEl = document.querySelector('h1.product-detail-info__header-name, .product-detail-info__header-name, h1[class*="product"]');
569 return titleEl?.textContent?.trim() || document.querySelector('h1')?.textContent?.trim();
570 },
571 price: () => {
572 const priceEl = document.querySelector('[data-qa*="price"], .money-amount, .price__amount, .current-price-elem');
573 return parsePrice(priceEl?.textContent);
574 },
575 image: () => {
576 return document.querySelector('.product-detail-images img, picture img, .media-image__image, img[class*="product"]')?.src ||
577 document.querySelector('main img')?.src;
578 },
579 getSizes: () => {
580 const addBtn = document.querySelector('button[data-qa-action="add-to-cart"]');
581 if (addBtn) addBtn.click();
582
583 return new Promise(resolve => {
584 setTimeout(() => {
585 const sizeElements = document.querySelectorAll('li.size-selector-sizes__size, .product-size-info__main-label');
586 const sizes = Array.from(sizeElements).map(li => {
587 const labelEl = li.querySelector('.size-selector-sizes-size__label') || li;
588 const label = labelEl?.textContent?.trim().replace(/coming\s?soon/gi, '').trim();
589
590 // Check if size is OUT OF STOCK (disabled/unavailable)
591 const isOutOfStock = li.classList.contains('size-selector-sizes-size--disabled') ||
592 li.classList.contains('size-selector-sizes-size--unavailable') ||
593 li.textContent?.toLowerCase().includes('coming soon');
594
595 // Check if size is IN STOCK (enabled and NOT disabled/unavailable)
596 const isInStock = li.classList.contains('size-selector-sizes-size--enabled') && !isOutOfStock;
597
598 return { label, element: li, inStock: isInStock };
599 }).filter(s => s.label && s.label.length <= 10);
600
601 console.log('[Stock Tracker] Zara found sizes:', sizes.length, sizes.map(s => `${s.label}: ${s.inStock ? 'IN STOCK' : 'OUT OF STOCK'}`));
602 resolve(sizes);
603 }, 1500);
604 });
605 }
606 },
607
608 // Bershka
609 'bershka.com': {
610 title: () => {
611 const titleEl = document.querySelector('h1.product-detail-info-layout__title, .product-detail-info-layout__title');
612 const title = titleEl?.textContent?.trim();
613 console.log('[Stock Tracker] Bershka title:', title);
614 return title;
615 },
616 price: () => {
617 const priceEl = document.querySelector('[data-qa-anchor="productItemPrice"], .current-price-elem, .price-elem');
618 const price = parsePrice(priceEl?.textContent);
619 console.log('[Stock Tracker] Bershka price:', price);
620 return price;
621 },
622 image: () => {
623 const img = document.querySelector('img[class*="product-detail"], img[class*="media-image"], picture img, main img')?.src;
624 console.log('[Stock Tracker] Bershka image:', img ? 'found' : 'not found');
625 return img;
626 },
627 getSizes: () => {
628 return new Promise(resolve => {
629 setTimeout(() => {
630 const sizeElements = document.querySelectorAll('button[data-qa-anchor="sizeListItem"], .ui--dot-item, button[aria-label*="מידה"]');
631 const sizes = Array.from(sizeElements).map(btn => {
632 const labelEl = btn.querySelector('.text__label') || btn;
633 const label = labelEl?.textContent?.trim() || btn.getAttribute('aria-label')?.replace('מידה ', '').trim();
634 const isOutOfStock = btn.disabled ||
635 btn.classList.contains('is-disabled') ||
636 btn.classList.contains('is-unavailable') ||
637 btn.getAttribute('aria-disabled') === 'true';
638
639 return { label, element: btn, inStock: !isOutOfStock };
640 }).filter(s => s.label && s.label.length <= 10);
641
642 console.log('[Stock Tracker] Bershka found sizes:', sizes.length, sizes.map(s => s.label));
643 resolve(sizes);
644 }, 1000);
645 });
646 }
647 },
648
649 // H&M
650 'hm.com': {
651 title: () => {
652 const titleEl = document.querySelector('h1.ProductName-module--productTitle, .ProductName-module--productTitle, h1[class*="product"]');
653 return titleEl?.textContent?.trim() || document.querySelector('h1')?.textContent?.trim();
654 },
655 price: () => {
656 const priceEl = document.querySelector('.ProductPrice-module--currentPrice, [data-testid="price"], .price');
657 return parsePrice(priceEl?.textContent);
658 },
659 image: () => {
660 return document.querySelector('.ProductImages-module--image img, figure img, img[class*="product"]')?.src ||
661 document.querySelector('main img')?.src;
662 },
663 getSizes: () => {
664 return new Promise(resolve => {
665 setTimeout(() => {
666 // H&M uses DIV elements with role="radio" for size selection
667 const sizeElements = document.querySelectorAll('div[data-testid^="sizeButton-"], div[role="radio"][id^="sizeButton"]');
668 const sizes = Array.from(sizeElements).map(div => {
669 // Extract size label from the inner div text
670 const label = div.querySelector('div')?.textContent?.trim().replace(/\s+/g, ' ').trim();
671
672 // Check stock status from aria-label
673 const ariaLabel = div.getAttribute('aria-label') || '';
674 const isOutOfStock = ariaLabel.toLowerCase().includes('není skladem') ||
675 ariaLabel.toLowerCase().includes('out of stock') ||
676 ariaLabel.toLowerCase().includes('not available') ||
677 div.classList.contains('cf813c') || // H&M's out-of-stock class
678 div.querySelector('.da0c1b'); // Strikethrough text class
679
680 return { label, element: div, inStock: !isOutOfStock };
681 }).filter(s => s.label && s.label.length <= 10);
682
683 console.log('[Stock Tracker] H&M found sizes:', sizes.length, sizes.map(s => `${s.label}: ${s.inStock ? 'IN STOCK' : 'OUT OF STOCK'}`));
684 resolve(sizes);
685 }, 1000);
686 });
687 }
688 },
689
690 // Nike
691 'nike.com': {
692 title: () => {
693 const titleEl = document.querySelector('h1#pdp_product_title, #pdp_product_title, h1[class*="product"]');
694 return titleEl?.textContent?.trim() || document.querySelector('h1')?.textContent?.trim();
695 },
696 price: () => {
697 const priceEl = document.querySelector('[data-test="product-price"], .product-price, [class*="price"]');
698 return parsePrice(priceEl?.textContent);
699 },
700 image: () => {
701 return document.querySelector('.product-image img, picture img, img[class*="product"]')?.src ||
702 document.querySelector('main img')?.src;
703 },
704 getSizes: () => {
705 return new Promise(resolve => {
706 setTimeout(() => {
707 const sizeElements = document.querySelectorAll('[data-qa="size-available"], .size-grid-button, button[class*="size"]');
708 const sizes = Array.from(sizeElements).map(btn => {
709 const label = btn.textContent?.trim();
710 const isOutOfStock = btn.disabled || btn.classList.contains('unavailable');
711 return { label, element: btn, inStock: !isOutOfStock };
712 }).filter(s => s.label && s.label.length <= 10);
713
714 console.log('[Stock Tracker] Nike found sizes:', sizes.length);
715 resolve(sizes);
716 }, 1000);
717 });
718 }
719 },
720
721 // Amazon
722 'amazon.com': {
723 title: () => {
724 const titleEl = document.querySelector('#productTitle, h1.product-title, h1[id*="product"]');
725 return titleEl?.textContent?.trim() || document.querySelector('h1')?.textContent?.trim();
726 },
727 price: () => {
728 const priceEl = document.querySelector('.a-price .a-offscreen, #priceblock_ourprice, .a-price-whole, [class*="price"]');
729 return parsePrice(priceEl?.textContent);
730 },
731 image: () => {
732 return document.querySelector('#landingImage, #imgBlkFront, img[class*="product"]')?.src ||
733 document.querySelector('main img')?.src;
734 },
735 getSizes: () => {
736 return new Promise(resolve => {
737 setTimeout(() => {
738 const sizeElements = document.querySelectorAll('#native_dropdown_selected_size_name option, .size-option, select[name*="size"] option');
739 const sizes = Array.from(sizeElements).map(opt => {
740 const label = opt.textContent?.trim();
741 const isOutOfStock = opt.disabled || label.toLowerCase().includes('out of stock');
742 return { label, element: opt, inStock: !isOutOfStock };
743 }).filter(s => s.label && !s.label.includes('Select') && s.label.length <= 10);
744
745 console.log('[Stock Tracker] Amazon found sizes:', sizes.length);
746 resolve(sizes);
747 }, 1000);
748 });
749 }
750 },
751
752 // Generic fallback for any site
753 'generic': {
754 title: () => {
755 // Try multiple selectors in order of specificity
756 const selectors = [
757 'h1[class*="product"][class*="title"]',
758 'h1[class*="product"][class*="name"]',
759 'h1.product-detail-info-layout__title',
760 '.product-detail-info-layout__title',
761 'h1[class*="product"]',
762 '[itemprop="name"]',
763 '.product-title',
764 '.product-name',
765 '[data-testid*="product"][data-testid*="title"]',
766 '[data-testid*="product"][data-testid*="name"]',
767 'h1'
768 ];
769
770 for (const selector of selectors) {
771 const el = document.querySelector(selector);
772 const text = el?.textContent?.trim();
773 if (text && text.length > 0 && text.length < 200) {
774 console.log('[Stock Tracker] Found title with selector:', selector, text);
775 return text;
776 }
777 }
778
779 console.log('[Stock Tracker] Using document.title as fallback');
780 return document.title;
781 },
782 price: () => {
783 const priceSelectors = [
784 '[data-qa-anchor="productItemPrice"]',
785 '.current-price-elem',
786 '[itemprop="price"]',
787 '[class*="price"]:not([class*="strike"]):not([class*="old"]):not([class*="original"])',
788 '[data-price]',
789 '[data-testid*="price"]',
790 '[id*="price"]',
791 'span[class*="current"]',
792 'div[class*="current"]',
793 '.product-price',
794 '.price'
795 ];
796
797 for (const selector of priceSelectors) {
798 const elements = document.querySelectorAll(selector);
799 for (const el of elements) {
800 const price = parsePrice(el.textContent || el.getAttribute('content') || el.getAttribute('data-price'));
801 if (price && price > 0) {
802 console.log('[Stock Tracker] Found price with selector:', selector, price);
803 return price;
804 }
805 }
806 }
807 console.log('[Stock Tracker] No price found');
808 return null;
809 },
810 image: () => {
811 const imageSelectors = [
812 'img[class*="product-detail"]',
813 'img[class*="media-image"]',
814 '[itemprop="image"]',
815 '.product-image img',
816 '.product-img img',
817 'img[class*="product"]',
818 '[data-testid*="product"] img',
819 'picture img',
820 'main img',
821 'article img'
822 ];
823
824 for (const selector of imageSelectors) {
825 const img = document.querySelector(selector);
826 if (img?.src && !img.src.includes('data:image')) {
827 console.log('[Stock Tracker] Found image with selector:', selector);
828 return img.src;
829 }
830 }
831
832 console.log('[Stock Tracker] No image found');
833 return null;
834 },
835 getSizes: () => {
836 return new Promise(resolve => {
837 setTimeout(() => {
838 const sizeElements = document.querySelectorAll(
839 'button[data-qa-anchor="sizeListItem"], ' +
840 'button[class*="size"]:not([class*="guide"]):not([class*="help"]), ' +
841 'button[aria-label*="מידה"], ' +
842 'button[aria-label*="size"], ' +
843 '.ui--dot-item, ' +
844 '.size-option, ' +
845 '[data-size], ' +
846 '[data-testid*="size"]:not([data-testid*="guide"]), ' +
847 'select[name*="size"] option, ' +
848 'select[id*="size"] option, ' +
849 'li[class*="size"]:not([class*="guide"]), ' +
850 'div[class*="size"][role="button"], ' +
851 'span[class*="size"][role="button"]'
852 );
853
854 const sizes = Array.from(sizeElements).map(el => {
855 let label = el.textContent?.trim() || el.getAttribute('data-size') || el.value;
856
857 // Extract size from aria-label if needed
858 if (!label || label.length > 20) {
859 const ariaLabel = el.getAttribute('aria-label');
860 if (ariaLabel) {
861 label = ariaLabel.replace(/מידה\s*/gi, '').replace(/size\s*/gi, '').trim();
862 }
863 }
864
865 // Clean up label
866 if (label) {
867 label = label.replace(/\s+/g, ' ').trim();
868 }
869
870 const isOutOfStock = el.disabled ||
871 el.classList.contains('disabled') ||
872 el.classList.contains('unavailable') ||
873 el.classList.contains('out-of-stock') ||
874 el.classList.contains('sold-out') ||
875 el.classList.contains('is-disabled') ||
876 el.classList.contains('is-unavailable') ||
877 el.getAttribute('aria-disabled') === 'true' ||
878 el.textContent?.toLowerCase().includes('out of stock') ||
879 el.textContent?.toLowerCase().includes('sold out') ||
880 el.textContent?.toLowerCase().includes('coming soon') ||
881 el.textContent?.toLowerCase().includes('unavailable');
882 return { label, element: el, inStock: !isOutOfStock };
883 }).filter(s => s.label && s.label.length > 0 && s.label.length <= 20 && !s.label.toLowerCase().includes('select') && !s.label.toLowerCase().includes('choose') && !s.label.toLowerCase().includes('guide'));
884
885 console.log('[Stock Tracker] Generic found sizes:', sizes.length, sizes.map(s => s.label));
886 resolve(sizes);
887 }, 1000);
888 });
889 }
890 }
891 };
892
893 const getAdapter = () => {
894 const host = location.host.replace('www.', '').replace('www2.', '');
895
896 // Try specific adapter first
897 for (const [domain, adapter] of Object.entries(adapters)) {
898 if (domain !== 'generic' && host.includes(domain)) {
899 return adapter;
900 }
901 }
902
903 // Fallback to generic adapter
904 return adapters.generic;
905 };
906
907 // CSS selector builder
908 function buildSelector(node) {
909 if (!node) return '';
910 if (node.id) return `#${CSS.escape(node.id)}`;
911
912 const parts = [];
913 let el = node;
914 let depth = 0;
915
916 while (el && el.nodeType === 1 && depth < 5) {
917 let seg = el.tagName.toLowerCase();
918
919 // For size selectors, use stable classes only (not status-dependent classes)
920 if (el.classList.length) {
921 const stableClasses = Array.from(el.classList).filter(c =>
922 !c.includes('disabled') &&
923 !c.includes('unavailable') &&
924 !c.includes('enabled') &&
925 !c.includes('selected') &&
926 !c.includes('active')
927 );
928 if (stableClasses.length > 0) {
929 seg += '.' + stableClasses.slice(0, 2).map(c => CSS.escape(c)).join('.');
930 }
931 }
932
933 const siblings = Array.from(el.parentElement?.children || []).filter(x => x.tagName === el.tagName);
934 if (siblings.length > 1) {
935 const idx = siblings.indexOf(el) + 1;
936 seg += `:nth-of-type(${idx})`;
937 }
938
939 parts.unshift(seg);
940 el = el.parentElement;
941 depth++;
942 }
943
944 return parts.join(' > ');
945 }
946
947 // Check stock from HTML
948 function checkStockFromHTML(html, selector) {
949 try {
950 const doc = new DOMParser().parseFromString(html, 'text/html');
951 const el = doc.querySelector(selector);
952 if (!el) return null; // לא מצאנו את האלמנט – לא משנים מצב
953
954 // 1) attributes הכי אמין
955 if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true') return false;
956
957 // 2) טקסט – נבדוק קודם ביטויים של "לא במלאי"
958 const txt = (el.textContent || '').toLowerCase().replace(/\s+/g, ' ').trim();
959 const cls = (el.className || '').toLowerCase();
960
961 const NEG = /(not\s+available|unavailable|sold\s*out|out\s*of\s*stock|coming\s*soon|notify\s*me|לא\s*במלאי|אזל\s*המלאי|אין\s*מלאי|غير متوفر|agotado|non\s+disponibile|nicht\s+verfügbar|épuisé)/i;
962 if (NEG.test(txt) || /(out-of-stock|sold-out|unavailable|is-unavailable|oos)/i.test(cls)) return false;
963
964 // 3) חיובי מפורש
965 const POS = /\b(in\s*stock|available\s+now|add\s*to\s*cart|buy\s*now)\b/i;
966 if (POS.test(txt) || /(available|is-available)/i.test(cls)) return true;
967
968 // לא הצלחנו להכריע – אל תשנה מצב קיים
969 return null;
970 } catch {
971 return null;
972 }
973 }
974
975 // Extract availability from JSON-LD structured data
976 function extractAvailabilityFromJSONLD(html) {
977 try {
978 const doc = new DOMParser().parseFromString(html, 'text/html');
979 const scripts = Array.from(doc.querySelectorAll('script[type="application/ld+json"]'));
980 const pairs = []; // [{label, inStock}]
981
982 for (const s of scripts) {
983 const txt = (s.textContent || '').trim();
984 if (!txt) continue;
985
986 const candidates = [];
987 try {
988 candidates.push(JSON.parse(txt));
989 } catch {
990 try {
991 candidates.push(JSON.parse('[' + txt.replace(/}\s*,\s*{/g, '},{') + ']'));
992 } catch {}
993 }
994
995 for (const root of candidates.flat()) {
996 const nodes = Array.isArray(root) ? root : [root];
997 for (const node of nodes) {
998 const offers = node?.offers ?? node?.Offers;
999 if (!offers) continue;
1000
1001 for (const off of (Array.isArray(offers) ? offers : [offers])) {
1002 const rawLabel = off?.size || off?.sku || off?.name || '';
1003 let avail = off?.availability ?? off?.Availability ?? '';
1004 if (!rawLabel) continue;
1005
1006 let inStock = null;
1007 if (typeof avail === 'string') {
1008 const a = avail.toLowerCase();
1009 if (a.includes('instock') || a.includes('limitedavailability')) inStock = true;
1010 else if (a.includes('outofstock') || a.includes('soldout')) inStock = false;
1011 } else if (typeof avail === 'boolean') {
1012 inStock = !!avail;
1013 }
1014
1015 if (inStock !== null) {
1016 const clean = String(rawLabel).trim();
1017 if (clean) pairs.push({ label: clean, inStock });
1018 }
1019 }
1020 }
1021 }
1022 }
1023
1024 return pairs.length ? pairs : null;
1025 } catch (e) {
1026 console.warn('[Stock Tracker] JSON-LD parse error:', e);
1027 return null;
1028 }
1029 }
1030
1031 // Extract Inditex (Zara/Bershka) offers from inline scripts
1032 function extractInditexOffers(html) {
1033 try {
1034 const doc = new DOMParser().parseFromString(html, 'text/html');
1035 const scripts = Array.from(doc.querySelectorAll('script'));
1036 const pairs = []; // [{label, inStock}]
1037
1038 const AVAIL_MAP = {
1039 IN_STOCK: true,
1040 LIMITED_STOCK: true,
1041 OUT_OF_STOCK: false,
1042 SOLD_OUT: false,
1043 UNAVAILABLE: false
1044 };
1045
1046 for (const s of scripts) {
1047 const txt = s.textContent || '';
1048 if (!txt || txt.length < 50) continue;
1049
1050 // 1) Try to catch records like {..."availability":"IN_STOCK"...,"size":"M"...}
1051 const objRegex = /\{[^{}]*"(?:availability|stockStatus)"\s*:\s*"([A-Z_]+)"[^{}]*"(?:size|sizeName|name|label|variantSize)"\s*:\s*"([^"]+)"[^{}]*\}/g;
1052 let m;
1053 while ((m = objRegex.exec(txt)) !== null) {
1054 const rawAvail = (m[1] || '').toUpperCase();
1055 const rawLabel = (m[2] || '').trim();
1056 if (!rawLabel) continue;
1057 const inStock = AVAIL_MAP.hasOwnProperty(rawAvail) ? AVAIL_MAP[rawAvail] : null;
1058 if (inStock !== null) pairs.push({ label: rawLabel, inStock });
1059 }
1060
1061 // 2) Sometimes built as array of skuStocks: [{"size":"M","availability":"IN_STOCK"}, ...]
1062 const arrMatch = txt.match(/"skuStocks?"\s*:\s*(\[[\s\S]*?\])/i);
1063 if (arrMatch) {
1064 try {
1065 const arr = JSON.parse(arrMatch[1]);
1066 for (const r of arr) {
1067 const rawLabel = (r.size || r.sizeName || r.name || r.label || '').trim();
1068 const rawAvail = String(r.availability || r.stockStatus || '').toUpperCase();
1069 if (!rawLabel) continue;
1070 const inStock = AVAIL_MAP.hasOwnProperty(rawAvail) ? AVAIL_MAP[rawAvail] : null;
1071 if (inStock !== null) pairs.push({ label: rawLabel, inStock });
1072 }
1073 } catch {}
1074 }
1075 }
1076
1077 // Remove duplicates - prefer IN_STOCK if there's a conflict
1078 const seen = new Map();
1079 for (const p of pairs) {
1080 const key = normalizeSizeLabel(p.label);
1081 if (!seen.has(key)) {
1082 seen.set(key, p);
1083 } else if (p.inStock === true) {
1084 seen.set(key, p); // Prefer IN_STOCK
1085 }
1086 }
1087
1088 const out = Array.from(seen.values());
1089 return out.length ? out : null;
1090 } catch (e) {
1091 console.warn('[Stock Tracker] extractInditexOffers error:', e);
1092 return null;
1093 }
1094 }
1095
1096 // Check stock from live DOM (when product page is open)
1097 function liveStockFromOpenTab(selector, url) {
1098 try {
1099 const here = location.href.split('#')[0].split('?')[0];
1100 if (here !== url) return null;
1101 const node = document.querySelector(selector);
1102 if (!node) return null;
1103
1104 // For Zara - check for --enabled class (positive indicator)
1105 if (node.classList.contains('size-selector-sizes-size--enabled')) {
1106 // Check if it's also disabled/unavailable
1107 const isDisabled = node.classList.contains('size-selector-sizes-size--disabled') ||
1108 node.classList.contains('size-selector-sizes-size--unavailable') ||
1109 node.textContent?.toLowerCase().includes('coming soon');
1110 return !isDisabled;
1111 }
1112
1113 // Standard checks for other sites
1114 const disabled = node.hasAttribute('disabled') || node.getAttribute('aria-disabled') === 'true';
1115 const txt = (node.textContent||'').toLowerCase();
1116 const cls = (node.className||'').toLowerCase();
1117 if (disabled) return false;
1118 if (/(sold\s?out|out\s?of\s?stock|unavailable|coming\s?soon|notify\s?me)/i.test(txt)) return false;
1119 if (/(out-of-stock|sold-out|unavailable|is-unavailable)/i.test(cls)) return false;
1120
1121 // Check for positive indicators
1122 if (/(in-stock|is-available|available)\b/i.test(cls)) return true;
1123
1124 // לא בטוח? אל תחרוץ - תחזיר null (לא true!)
1125 return null;
1126 } catch { return null; }
1127 }
1128
1129 // Fetch page HTML
1130 async function fetchHTML(url) {
1131 return new Promise((resolve, reject) => {
1132 GM.xmlhttpRequest({
1133 method: 'GET',
1134 url,
1135 timeout: 30000,
1136 onload: (res) => {
1137 if (res.status >= 200 && res.status < 300) {
1138 resolve(res.responseText);
1139 } else {
1140 reject(new Error(`HTTP ${res.status}`));
1141 }
1142 },
1143 onerror: (err) => reject(new Error('Network error: ' + (err?.error || 'Unknown'))),
1144 ontimeout: () => reject(new Error('Timeout'))
1145 });
1146 });
1147 }
1148
1149 // WhatsApp helpers
1150 function openWhatsApp(text, phone = '') {
1151 const url = new URL('https://web.whatsapp.com/send');
1152 if (phone) url.searchParams.set('phone', phone.replace(/[^\d]/g, ''));
1153 url.searchParams.set('text', text);
1154
1155 (async () => {
1156 await GM.setValue('wa_pending_text', text);
1157 await GM.setValue('wa_pending_send', 'true');
1158 GM.openInTab(url.toString(), false);
1159 })();
1160 }
1161
1162 async function autoSendWhatsApp() {
1163 if (!location.host.includes('web.whatsapp.com')) return;
1164
1165 const shouldSend = await GM.getValue('wa_pending_send');
1166 const text = await GM.getValue('wa_pending_text');
1167
1168 if (shouldSend === 'true' && text) {
1169 await GM.setValue('wa_pending_send', '');
1170 await GM.setValue('wa_pending_text', '');
1171
1172 console.log('[Stock Tracker] Auto-sending WhatsApp message...');
1173 console.log('[Stock Tracker] Message text:', text);
1174
1175 let attempts = 0;
1176 const checkComposer = setInterval(async () => {
1177 attempts++;
1178 console.log('[Stock Tracker] Attempt', attempts, '/ 30');
1179
1180 // Try multiple selectors for the composer
1181 const composer = document.querySelector('footer div[contenteditable="true"][role="textbox"]') ||
1182 document.querySelector('div[contenteditable="true"][data-tab="10"]') ||
1183 document.querySelector('div[contenteditable="true"][data-lexical-editor="true"]');
1184
1185 if (composer && composer.offsetParent !== null) {
1186 clearInterval(checkComposer);
1187 console.log('[Stock Tracker] Composer found!');
1188
1189 composer.focus();
1190 await new Promise(resolve => setTimeout(resolve, 500));
1191
1192 composer.innerHTML = '';
1193
1194 const lines = text.split('\n');
1195 lines.forEach((line, idx) => {
1196 if (idx > 0) composer.appendChild(document.createElement('br'));
1197 composer.appendChild(document.createTextNode(line));
1198 });
1199
1200 composer.dispatchEvent(new InputEvent('input', { bubbles: true }));
1201 composer.dispatchEvent(new Event('change', { bubbles: true }));
1202
1203 console.log('[Stock Tracker] Text inserted, waiting before sending...');
1204 await new Promise(resolve => setTimeout(resolve, 1500));
1205
1206 // Try multiple selectors for send button - including DIV with role="button"
1207 const sendBtn = document.querySelector('footer div[aria-label="שליחה"][role="button"]') ||
1208 document.querySelector('footer [aria-label="שליחה"][role="button"]') ||
1209 document.querySelector('div[aria-label="שליחה"][role="button"]') ||
1210 document.querySelector('button[aria-label="שליחה"]') ||
1211 document.querySelector('button[aria-label*="Send"]') ||
1212 document.querySelector('footer button[aria-label="שליחה"]') ||
1213 document.querySelector('footer button[aria-label*="Send"]') ||
1214 document.querySelector('span[data-icon="send"]')?.closest('button') ||
1215 document.querySelector('span[data-icon="send"]')?.closest('div[role="button"]') ||
1216 document.querySelector('button[data-tab="11"]');
1217
1218 if (sendBtn) {
1219 console.log('[Stock Tracker] Send button found, clicking...');
1220 sendBtn.click();
1221 console.log('[Stock Tracker] ✅ Message sent!');
1222 } else {
1223 console.error('[Stock Tracker] Send button not found!');
1224 console.log('[Stock Tracker] Trying to find any button in footer...');
1225 const allFooterButtons = document.querySelectorAll('footer button, footer div[role="button"]');
1226 console.log('[Stock Tracker] Found', allFooterButtons.length, 'buttons in footer');
1227 }
1228 } else {
1229 if (attempts % 5 === 0) {
1230 console.log('[Stock Tracker] Still waiting for composer...');
1231 }
1232 }
1233
1234 if (attempts >= 30) {
1235 clearInterval(checkComposer);
1236 console.error('[Stock Tracker] Timeout: Could not find composer after 30 attempts');
1237 }
1238 }, 1000);
1239 }
1240 }
1241
1242 // Check stock for all items
1243 async function checkAllStock() {
1244 const items = await getItems();
1245 const prefs = await getPrefs();
1246
1247 if (items.length === 0) {
1248 showToast('אין מוצרים במעקב', 'error');
1249 return;
1250 }
1251
1252 // Update status indicator
1253 const statusText = document.querySelector('#st-status-text');
1254 if (statusText) {
1255 statusText.textContent = '🟡 בודק מלאי...';
1256 statusText.style.color = '#fbbf24';
1257 }
1258
1259 showToast(`🔄 בודק ${items.length} מוצרים...`, 'info');
1260 console.log('[Stock Tracker] ========================================');
1261 console.log('[Stock Tracker] Starting stock check for', items.length, 'items...');
1262 console.log('[Stock Tracker] Current time:', new Date().toLocaleString('he-IL'));
1263 console.log('[Stock Tracker] ========================================');
1264
1265 let foundInStock = 0;
1266 let updatedItems = [...items];
1267 let successCount = 0;
1268 let failCount = 0;
1269
1270 for (let i = 0; i < updatedItems.length; i++) {
1271 const item = updatedItems[i];
1272 console.log(`[Stock Tracker] [${i+1}/${updatedItems.length}] Checking: ${item.title}`);
1273 console.log(`[Stock Tracker] URL: ${item.url}`);
1274
1275 let retries = 3;
1276 let success = false;
1277
1278 while (retries > 0 && !success) {
1279 try {
1280 console.log(`[Stock Tracker] Fetching HTML (attempt ${4-retries}/3)...`);
1281 const html = await fetchHTML(item.url);
1282 console.log(`[Stock Tracker] HTML fetched successfully (${html.length} chars)`);
1283
1284 // Check if this is an Inditex site (Zara/Bershka)
1285 const isInditex = item.url.includes('bershka.com') || item.url.includes('zara.com');
1286
1287 // Try Inditex-specific extraction first (for Zara/Bershka)
1288 const inditexPairs = isInditex ? extractInditexOffers(html) : null;
1289 if (inditexPairs) {
1290 console.log('[Stock Tracker] Inditex pairs:', inditexPairs.map(p => `${p.label}:${p.inStock ? 'IN' : 'OUT'}`));
1291 }
1292
1293 // Try JSON-LD as fallback
1294 const jsonLdPairs = extractAvailabilityFromJSONLD(html);
1295 if (jsonLdPairs) {
1296 console.log('[Stock Tracker] JSON-LD pairs:', jsonLdPairs.map(p => `${p.label}:${p.inStock ? 'IN' : 'OUT'}`));
1297 }
1298
1299 for (const size of item.sizes) {
1300 console.log(`[Stock Tracker] Checking size: ${size.label} (was: ${size.inStock ? 'IN STOCK' : size.inStock === false ? 'OUT OF STOCK' : 'UNKNOWN'})`);
1301
1302 let newStock = null;
1303
1304 // 1) Inditex-specific extraction (highest priority for Zara/Bershka)
1305 if (inditexPairs) {
1306 const hit = inditexPairs.find(p => sizeMatches(p.label, size.label));
1307 if (hit) {
1308 newStock = !!hit.inStock;
1309 console.log(`[Stock Tracker] Inditex matched "${size.label}" ⇢ ${newStock ? 'IN' : 'OUT'} STOCK (source "${hit.label}")`);
1310 }
1311 }
1312
1313 // 2) JSON-LD (if no Inditex match)
1314 if (newStock === null && jsonLdPairs) {
1315 const hit = jsonLdPairs.find(p => sizeMatches(p.label, size.label));
1316 if (hit) {
1317 newStock = !!hit.inStock;
1318 console.log(`[Stock Tracker] JSON-LD matched "${size.label}" ⇢ ${newStock ? 'IN' : 'OUT'} STOCK (source "${hit.label}")`);
1319 }
1320 }
1321
1322 // 3) Live DOM (only if we're on the product page)
1323 if (newStock === null) {
1324 newStock = liveStockFromOpenTab(size.selector, item.url);
1325 if (newStock !== null) {
1326 console.log(`[Stock Tracker] Live DOM: ${newStock ? 'IN' : 'OUT'} STOCK`);
1327 }
1328 }
1329
1330 // 4) HTML static – don't run on Inditex (causes false positives)
1331 if (newStock === null && !isInditex) {
1332 newStock = checkStockFromHTML(html, size.selector);
1333 console.log(`[Stock Tracker] HTML static: ${newStock === null ? 'NULL' : newStock ? 'IN' : 'OUT'}`);
1334 }
1335
1336 if (newStock === null) {
1337 console.warn('[Stock Tracker] Could not determine stock for:', size.label, 'URL=', item.url);
1338 }
1339
1340 const prev = size.inStock;
1341 const now = newStock !== null ? newStock : prev;
1342
1343 // טריגר כשעוברים מ-(false או null) → true
1344 if ((prev === false || prev == null) && now === true) {
1345 foundInStock++;
1346 console.log('[Stock Tracker] 🔔🔔🔔 BACK IN STOCK DETECTED! 🔔🔔🔔');
1347 console.log('[Stock Tracker] Product:', item.title);
1348 console.log('[Stock Tracker] Size:', size.label);
1349 console.log('[Stock Tracker] Price:', item.price);
1350
1351 const message = `🔔 חזרה למלאי!\n\n${item.title}\nמידה: ${size.label}\nמחיר: ${item.price}\n\n${item.url}`;
1352
1353 if (prefs.waPhone) {
1354 console.log('[Stock Tracker] Sending WhatsApp to:', prefs.waPhone);
1355 openWhatsApp(message, prefs.waPhone);
1356 } else {
1357 console.log('[Stock Tracker] No WhatsApp phone configured');
1358 }
1359
1360 if (prefs.notifications) {
1361 showToast(`🔔 ${size.label} חזרה למלאי!`, 'success');
1362 }
1363
1364 if (prefs.soundAlerts) {
1365 playNotificationSound();
1366 }
1367
1368 // Auto-add to cart if enabled
1369 if (prefs.autoAddToCart) {
1370 queueAutoCart({
1371 url: item.url,
1372 size: size.label,
1373 site: item.url.includes('zara.com') ? 'zara' : item.url.includes('bershka.com') ? 'bershka' : 'generic',
1374 title: item.title
1375 });
1376 }
1377 }
1378
1379 size.inStock = now;
1380 size.lastChecked = Date.now();
1381 }
1382
1383 success = true;
1384 successCount++;
1385 console.log(`[Stock Tracker] ✅ Successfully checked: ${item.title}`);
1386
1387 } catch (err) {
1388 retries--;
1389 console.error(`[Stock Tracker] ❌ Error checking ${item.title} (attempt ${3-retries}/3):`, err);
1390
1391 if (retries > 0) {
1392 console.log('[Stock Tracker] Waiting 2 seconds before retry...');
1393 await new Promise(resolve => setTimeout(resolve, 2000));
1394 } else {
1395 failCount++;
1396 console.error('[Stock Tracker] ❌ Failed after 3 retries:', item.title);
1397 }
1398 }
1399 }
1400 console.log('[Stock Tracker] ----------------------------------------');
1401 }
1402
1403 await saveItems(updatedItems);
1404
1405 // Update status indicator
1406 if (statusText) {
1407 statusText.textContent = '🟢 המערכת פעילה';
1408 statusText.style.color = '#22c55e';
1409 }
1410
1411 console.log('[Stock Tracker] ========================================');
1412 console.log('[Stock Tracker] Stock check completed!');
1413 console.log('[Stock Tracker] Success:', successCount, '| Failed:', failCount);
1414 console.log('[Stock Tracker] Found in stock:', foundInStock);
1415 console.log('[Stock Tracker] ========================================');
1416
1417 if (foundInStock > 0) {
1418 showToast(`✅ נמצאו ${foundInStock} מידות במלאי!`, 'success');
1419 } else {
1420 showToast(`✅ בדיקה הושלמה (${successCount}/${items.length} הצליחו)`, 'info');
1421 }
1422
1423 if (failCount > 0) {
1424 console.warn(`[Stock Tracker] ${failCount} items failed to check`);
1425 }
1426
1427 await renderList();
1428 await renderStats();
1429 await updateFabBadge();
1430 }
1431
1432 // Check stock for a single item
1433 async function checkSingleItem(itemId) {
1434 const items = await getItems();
1435 const prefs = await getPrefs();
1436 const item = items.find(i => i.id === itemId);
1437
1438 if (!item) {
1439 showToast('⚠️ מוצר לא נמצא', 'error');
1440 return;
1441 }
1442
1443 showToast(`🔄 בודק: ${item.title}...`, 'info');
1444 console.log('[Stock Tracker] Checking single item:', item.title);
1445
1446 let retries = 3;
1447 let success = false;
1448
1449 while (retries > 0 && !success) {
1450 try {
1451 const html = await fetchHTML(item.url);
1452 const isInditex = item.url.includes('bershka.com') || item.url.includes('zara.com');
1453 const inditexPairs = isInditex ? extractInditexOffers(html) : null;
1454 const jsonLdPairs = extractAvailabilityFromJSONLD(html);
1455
1456 let foundInStock = 0;
1457
1458 for (const size of item.sizes) {
1459 let newStock = null;
1460
1461 if (inditexPairs) {
1462 const hit = inditexPairs.find(p => sizeMatches(p.label, size.label));
1463 if (hit) newStock = !!hit.inStock;
1464 }
1465
1466 if (newStock === null && jsonLdPairs) {
1467 const hit = jsonLdPairs.find(p => sizeMatches(p.label, size.label));
1468 if (hit) newStock = !!hit.inStock;
1469 }
1470
1471 if (newStock === null) {
1472 newStock = liveStockFromOpenTab(size.selector, item.url);
1473 }
1474
1475 if (newStock === null && !isInditex) {
1476 newStock = checkStockFromHTML(html, size.selector);
1477 }
1478
1479 const prev = size.inStock;
1480 const now = newStock !== null ? newStock : prev;
1481
1482 if ((prev === false || prev == null) && now === true) {
1483 foundInStock++;
1484 const message = `🔔 חזרה למלאי!\n\n${item.title}\nמידה: ${size.label}\nמחיר: ${item.price}\n\n${item.url}`;
1485
1486 if (prefs.waPhone) {
1487 openWhatsApp(message, prefs.waPhone);
1488 }
1489
1490 if (prefs.notifications) {
1491 showToast(`🔔 ${size.label} חזרה למלאי!`, 'success');
1492 }
1493
1494 if (prefs.soundAlerts) {
1495 playNotificationSound();
1496 }
1497 }
1498
1499 size.inStock = now;
1500 size.lastChecked = Date.now();
1501 }
1502
1503 await saveItems(items);
1504 success = true;
1505
1506 if (foundInStock > 0) {
1507 showToast(`✅ נמצאו ${foundInStock} מידות במלאי!`, 'success');
1508 } else {
1509 showToast('✅ בדיקה הושלמה - אין שינויים', 'info');
1510 }
1511
1512 } catch (err) {
1513 retries--;
1514 console.error(`[Stock Tracker] Error checking item (attempt ${3-retries}/3):`, err);
1515
1516 if (retries === 0) {
1517 showToast('❌ שגיאה בבדיקת המוצר', 'error');
1518 }
1519 }
1520 }
1521
1522 await renderList();
1523 await renderStats();
1524 await updateFabBadge();
1525 }
1526
1527 // Export data
1528 async function exportData() {
1529 const items = await getItems();
1530 const prefs = await getPrefs();
1531
1532 const data = {
1533 items,
1534 prefs,
1535 exportDate: new Date().toISOString(),
1536 version: '1.0'
1537 };
1538
1539 const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
1540 const url = URL.createObjectURL(blob);
1541 const a = document.createElement('a');
1542 a.href = url;
1543 a.download = `stock-tracker-backup-${Date.now()}.json`;
1544 a.click();
1545 URL.revokeObjectURL(url);
1546
1547 showToast('✅ הנתונים יוצאו בהצלחה!', 'success');
1548 }
1549
1550 // Import data
1551 async function importData() {
1552 const input = document.createElement('input');
1553 input.type = 'file';
1554 input.accept = 'application/json';
1555
1556 input.onchange = async (e) => {
1557 const file = e.target.files[0];
1558 if (!file) return;
1559
1560 try {
1561 const text = await file.text();
1562 const data = JSON.parse(text);
1563
1564 if (data.items) await saveItems(data.items);
1565 if (data.prefs) await savePrefs(data.prefs);
1566
1567 await renderList();
1568 await loadPrefsToUI();
1569 await updateFabBadge();
1570 showToast('✅ הנתונים יובאו בהצלחה!', 'success');
1571 } catch (err) {
1572 console.error('[Stock Tracker] Import error:', err);
1573 showToast('❌ שגיאה בייבוא הנתונים', 'error');
1574 }
1575 };
1576
1577 input.click();
1578 }
1579
1580 // Clear all data
1581 async function clearAllData() {
1582 if (!confirm('האם אתה בטוח שברצונך למחוק את כל הנתונים?')) return;
1583
1584 await saveItems([]);
1585 await renderList();
1586 await renderStats();
1587 await updateFabBadge();
1588 showToast('🗑️ כל הנתונים נמחקו', 'info');
1589 }
1590
1591 // Quick add from current page
1592 async function quickAddAllSizes() {
1593 const adapter = getAdapter();
1594 if (!adapter) {
1595 showToast('⚠️ אתר זה לא נתמך כרגע', 'error');
1596 return;
1597 }
1598
1599 const title = adapter.title();
1600 const price = adapter.price();
1601 const image = adapter.image();
1602 const url = location.href.split('#')[0].split('?')[0];
1603
1604 if (!title) {
1605 showToast('⚠️ לא הצלחתי לזהות את המוצר', 'error');
1606 return;
1607 }
1608
1609 showToast('⏳ מוסיף את כל המידות...', 'info');
1610
1611 const sizes = await adapter.getSizes();
1612
1613 if (!sizes || sizes.length === 0) {
1614 showToast('⚠️ לא נמצאו מידות', 'error');
1615 return;
1616 }
1617
1618 const items = await getItems();
1619
1620 const selectedSizeData = sizes.map(size => ({
1621 label: size.label,
1622 selector: buildSelector(size.element),
1623 inStock: size.inStock,
1624 lastChecked: Date.now()
1625 }));
1626
1627 items.push({
1628 id: Date.now().toString(),
1629 title,
1630 price: price || 'לא זמין',
1631 image,
1632 url,
1633 sizes: selectedSizeData,
1634 addedAt: Date.now()
1635 });
1636
1637 await saveItems(items);
1638 await renderList();
1639 await renderStats();
1640 await updateFabBadge();
1641 showToast(`✅ נוספו ${sizes.length} מידות למעקב!`, 'success');
1642 }
1643
1644 // Copy product link
1645 async function copyProductLink(url) {
1646 try {
1647 await GM.setClipboard(url);
1648 showToast('✅ הקישור הועתק ללוח!', 'success');
1649 } catch (err) {
1650 console.error('[Stock Tracker] Copy error:', err);
1651 showToast('❌ שגיאה בהעתקת הקישור', 'error');
1652 }
1653 }
1654
1655 // UI Styles
1656 const styles = `
1657 .st-fab {
1658 position: fixed;
1659 right: 20px;
1660 bottom: 20px;
1661 z-index: 999999;
1662 background: linear-gradient(135deg, #25D366 0%, #22c55e 100%);
1663 color: #fff;
1664 border: none;
1665 border-radius: 50px;
1666 padding: 16px 24px;
1667 font-weight: 800;
1668 font-size: 16px;
1669 cursor: pointer;
1670 box-shadow: 0 8px 24px rgba(37, 211, 102, 0.4);
1671 transition: all 0.3s ease;
1672 }
1673 .st-fab:hover {
1674 transform: translateY(-2px);
1675 box-shadow: 0 12px 32px rgba(37, 211, 102, 0.6);
1676 }
1677
1678 .st-fab-alert {
1679 animation: pulse 2s infinite;
1680 }
1681
1682 @keyframes pulse {
1683 0%, 100% {
1684 box-shadow: 0 8px 24px rgba(37, 211, 102, 0.4);
1685 }
1686 50% {
1687 box-shadow: 0 8px 24px rgba(239, 68, 68, 0.6);
1688 }
1689 }
1690
1691 .st-badge {
1692 position: absolute;
1693 top: -8px;
1694 right: -8px;
1695 background: #ef4444;
1696 color: #fff;
1697 border-radius: 50%;
1698 width: 24px;
1699 height: 24px;
1700 display: flex;
1701 align-items: center;
1702 justify-content: center;
1703 font-size: 12px;
1704 font-weight: 800;
1705 border: 2px solid #fff;
1706 animation: bounce 1s infinite;
1707 }
1708
1709 @keyframes bounce {
1710 0%, 100% {
1711 transform: scale(1);
1712 }
1713 50% {
1714 transform: scale(1.1);
1715 }
1716 }
1717
1718 .st-panel {
1719 position: fixed;
1720 right: 20px;
1721 top: 50%;
1722 transform: translateY(-50%);
1723 z-index: 999999;
1724 background: linear-gradient(180deg, #1a1a1a 0%, #0a0a0a 100%);
1725 color: #fff;
1726 border: 1px solid #333;
1727 border-radius: 20px;
1728 width: 520px;
1729 max-height: 90vh;
1730 overflow: hidden;
1731 display: none;
1732 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8);
1733 }
1734
1735 .st-header {
1736 padding: 24px;
1737 background: linear-gradient(135deg, #25D366 0%, #22c55e 100%);
1738 display: flex;
1739 justify-content: space-between;
1740 align-items: center;
1741 font-weight: 800;
1742 font-size: 20px;
1743 }
1744
1745 .st-body {
1746 padding: 24px;
1747 max-height: calc(90vh - 90px);
1748 overflow-y: auto;
1749 }
1750
1751 .st-body::-webkit-scrollbar {
1752 width: 8px;
1753 }
1754
1755 .st-body::-webkit-scrollbar-track {
1756 background: #1a1a1a;
1757 }
1758
1759 .st-body::-webkit-scrollbar-thumb {
1760 background: #444;
1761 border-radius: 4px;
1762 }
1763
1764 .st-body::-webkit-scrollbar-thumb:hover {
1765 background: #555;
1766 }
1767
1768 .st-input {
1769 width: 100%;
1770 background: #2a2a2a;
1771 color: #fff;
1772 border: 1px solid #444;
1773 border-radius: 10px;
1774 padding: 14px 16px;
1775 margin: 8px 0;
1776 font-size: 14px;
1777 box-sizing: border-box;
1778 transition: all 0.2s;
1779 }
1780
1781 .st-input:focus {
1782 outline: none;
1783 border-color: #25D366;
1784 box-shadow: 0 0 0 3px rgba(37, 211, 102, 0.1);
1785 }
1786
1787 .st-checkbox {
1788 display: flex;
1789 align-items: center;
1790 gap: 12px;
1791 margin: 12px 0;
1792 cursor: pointer;
1793 padding: 10px;
1794 border-radius: 8px;
1795 transition: background 0.2s;
1796 }
1797
1798 .st-checkbox:hover {
1799 background: rgba(255, 255, 255, 0.05);
1800 }
1801
1802 .st-checkbox input {
1803 width: 20px;
1804 height: 20px;
1805 cursor: pointer;
1806 accent-color: #25D366;
1807 }
1808
1809 .st-btn {
1810 background: linear-gradient(135deg, #25D366 0%, #22c55e 100%);
1811 color: #fff;
1812 border: none;
1813 border-radius: 10px;
1814 padding: 14px 20px;
1815 font-weight: 700;
1816 cursor: pointer;
1817 width: 100%;
1818 margin: 8px 0;
1819 font-size: 14px;
1820 transition: all 0.2s ease;
1821 }
1822 .st-btn:hover {
1823 transform: translateY(-2px);
1824 box-shadow: 0 6px 16px rgba(37, 211, 102, 0.4);
1825 }
1826
1827 .st-btn-secondary {
1828 background: #2a2a2a;
1829 border: 1px solid #444;
1830 }
1831 .st-btn-secondary:hover {
1832 background: #333;
1833 box-shadow: 0 4px 12px rgba(255, 255, 255, 0.1);
1834 }
1835
1836 .st-btn-danger {
1837 background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
1838 }
1839
1840 .st-btn-danger:hover {
1841 box-shadow: 0 6px 16px rgba(239, 68, 68, 0.4);
1842 }
1843
1844 .st-item {
1845 background: linear-gradient(135deg, #2a2a2a 0%, #252525 100%);
1846 border: 1px solid #444;
1847 border-radius: 16px;
1848 padding: 20px;
1849 margin: 16px 0;
1850 transition: all 0.3s;
1851 }
1852
1853 .st-item:hover {
1854 border-color: #25D366;
1855 box-shadow: 0 8px 24px rgba(37, 211, 102, 0.15);
1856 transform: translateY(-2px);
1857 }
1858
1859 .st-size {
1860 display: inline-block;
1861 background: #1a1a1a;
1862 border: 2px solid #444;
1863 border-radius: 10px;
1864 padding: 10px 14px;
1865 margin: 6px 4px;
1866 font-size: 13px;
1867 font-weight: 700;
1868 cursor: pointer;
1869 transition: all 0.2s;
1870 }
1871
1872 .st-size.in-stock {
1873 border-color: #22c55e;
1874 color: #22c55e;
1875 background: rgba(34, 197, 94, 0.1);
1876 }
1877
1878 .st-size.out-of-stock {
1879 border-color: #ef4444;
1880 color: #ef4444;
1881 background: rgba(239, 68, 68, 0.1);
1882 }
1883
1884 .st-size:hover {
1885 transform: scale(1.08);
1886 }
1887
1888 .st-toast {
1889 position: fixed;
1890 top: 20px;
1891 right: 20px;
1892 background: #1a1a1a;
1893 color: #fff;
1894 padding: 16px 20px;
1895 border-radius: 12px;
1896 z-index: 9999999;
1897 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
1898 font-weight: 700;
1899 border-left: 4px solid #25D366;
1900 animation: slideIn 0.3s ease;
1901 }
1902
1903 @keyframes slideIn {
1904 from {
1905 transform: translateX(400px);
1906 opacity: 0;
1907 }
1908 to {
1909 transform: translateX(0);
1910 opacity: 1;
1911 }
1912 }
1913
1914 .st-toast.error {
1915 border-left-color: #ef4444;
1916 }
1917
1918 .st-toast.info {
1919 border-left-color: #3b82f6;
1920 }
1921
1922 .st-stats {
1923 background: linear-gradient(135deg, #2a2a2a 0%, #252525 100%);
1924 border: 1px solid #444;
1925 border-radius: 16px;
1926 padding: 20px;
1927 margin: 16px 0;
1928 display: grid;
1929 grid-template-columns: repeat(3, 1fr);
1930 gap: 16px;
1931 text-align: center;
1932 }
1933
1934 .st-stat {
1935 padding: 16px;
1936 background: #1a1a1a;
1937 border-radius: 12px;
1938 transition: all 0.2s;
1939 }
1940
1941 .st-stat:hover {
1942 background: #222;
1943 transform: translateY(-2px);
1944 }
1945
1946 .st-stat-value {
1947 font-size: 28px;
1948 font-weight: 800;
1949 color: #25D366;
1950 margin-bottom: 4px;
1951 }
1952
1953 .st-stat-label {
1954 font-size: 12px;
1955 color: #999;
1956 margin-top: 4px;
1957 }
1958
1959 .st-section {
1960 margin: 24px 0;
1961 }
1962
1963 .st-section-title {
1964 font-size: 14px;
1965 font-weight: 700;
1966 color: #999;
1967 text-transform: uppercase;
1968 letter-spacing: 1px;
1969 margin-bottom: 12px;
1970 }
1971
1972 .st-info-box {
1973 background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
1974 border: 1px solid #3b82f6;
1975 border-radius: 12px;
1976 padding: 16px;
1977 margin: 16px 0;
1978 }
1979
1980 .st-info-title {
1981 font-weight: 700;
1982 margin-bottom: 8px;
1983 color: #60a5fa;
1984 font-size: 14px;
1985 }
1986
1987 .st-info-content {
1988 font-size: 12px;
1989 color: #93c5fd;
1990 line-height: 1.6;
1991 }
1992
1993 .st-countdown-box {
1994 text-align: center;
1995 padding: 16px;
1996 background: linear-gradient(135deg, #2a2a2a 0%, #252525 100%);
1997 border-radius: 12px;
1998 margin: 16px 0;
1999 border: 1px solid #444;
2000 }
2001
2002 .st-countdown-label {
2003 color: #999;
2004 font-size: 12px;
2005 margin-bottom: 8px;
2006 }
2007
2008 .st-countdown-time {
2009 color: #25D366;
2010 font-weight: 700;
2011 font-size: 24px;
2012 }
2013
2014 .st-button-grid {
2015 display: grid;
2016 grid-template-columns: repeat(5, 1fr);
2017 gap: 8px;
2018 margin-top: 16px;
2019 }
2020
2021 .st-button-grid button {
2022 padding: 10px 8px;
2023 font-size: 11px;
2024 white-space: nowrap;
2025 }
2026 `;
2027
2028 // Create UI
2029 function createUI() {
2030 const styleEl = document.createElement('style');
2031 styleEl.textContent = styles;
2032 document.head.appendChild(styleEl);
2033
2034 const fab = document.createElement('button');
2035 fab.className = 'st-fab';
2036 fab.innerHTML = '📦 מעקב מלאי';
2037 document.body.appendChild(fab);
2038
2039 // Make FAB draggable
2040 let isDragging = false;
2041 let hasMoved = false;
2042 let currentX;
2043 let currentY;
2044 let initialX;
2045 let initialY;
2046 let xOffset = 0;
2047 let yOffset = 0;
2048
2049 fab.addEventListener('mousedown', dragStart);
2050 document.addEventListener('mousemove', drag);
2051 document.addEventListener('mouseup', dragEnd);
2052
2053 fab.addEventListener('touchstart', dragStart);
2054 document.addEventListener('touchmove', drag);
2055 document.addEventListener('touchend', dragEnd);
2056
2057 function dragStart(e) {
2058 if (e.type === 'touchstart') {
2059 initialX = e.touches[0].clientX - xOffset;
2060 initialY = e.touches[0].clientY - yOffset;
2061 } else {
2062 initialX = e.clientX - xOffset;
2063 initialY = e.clientY - yOffset;
2064 }
2065
2066 if (e.target === fab || fab.contains(e.target)) {
2067 isDragging = true;
2068 hasMoved = false;
2069 }
2070 }
2071
2072 function drag(e) {
2073 if (isDragging) {
2074 e.preventDefault();
2075
2076 if (e.type === 'touchmove') {
2077 currentX = e.touches[0].clientX - initialX;
2078 currentY = e.touches[0].clientY - initialY;
2079 } else {
2080 currentX = e.clientX - initialX;
2081 currentY = e.clientY - initialY;
2082 }
2083
2084 // Check if moved more than 5px
2085 const distance = Math.sqrt(Math.pow(currentX - xOffset, 2) + Math.pow(currentY - yOffset, 2));
2086 if (distance > 5) {
2087 hasMoved = true;
2088 }
2089
2090 xOffset = currentX;
2091 yOffset = currentY;
2092
2093 setTranslate(currentX, currentY, fab);
2094 }
2095 }
2096
2097 function dragEnd() {
2098 if (isDragging) {
2099 initialX = currentX;
2100 initialY = currentY;
2101 isDragging = false;
2102
2103 // Save position
2104 (async () => {
2105 await GM.setValue('fab_position', JSON.stringify({ x: xOffset, y: yOffset }));
2106 })();
2107 }
2108 }
2109
2110 function setTranslate(xPos, yPos, el) {
2111 el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
2112 }
2113
2114 // Restore saved position
2115 (async () => {
2116 const savedPos = await GM.getValue('fab_position');
2117 if (savedPos) {
2118 const pos = JSON.parse(savedPos);
2119 xOffset = pos.x;
2120 yOffset = pos.y;
2121 setTranslate(pos.x, pos.y, fab);
2122 }
2123 })();
2124
2125 const panel = document.createElement('div');
2126 panel.className = 'st-panel';
2127 panel.innerHTML = `
2128 <div class="st-header">
2129 <div>📦 מעקב מלאי + WhatsApp</div>
2130 <button class="st-btn-secondary" id="st-close" style="padding:8px 12px;border-radius:50%;width:auto">✕</button>
2131 </div>
2132 <div class="st-body">
2133 <div id="st-stats"></div>
2134
2135 <div style="margin-bottom:20px">
2136 <label style="display:block;margin-bottom:8px;font-weight:700">📱 מספר WhatsApp</label>
2137 <input type="text" class="st-input" id="st-phone" placeholder="972501234567">
2138 </div>
2139
2140 <div style="margin-bottom:20px">
2141 <label style="display:block;margin-bottom:8px;font-weight:700">⏱️ בדוק מלאי כל (דקות)</label>
2142 <input type="number" class="st-input" id="st-interval" placeholder="5" min="1" value="5">
2143 <p style="color:#999;font-size:12px;margin:5px 0 0 0">מינימום: 1 דקה | ברירת מחדל: 5 דקות</p>
2144 </div>
2145
2146 <label class="st-checkbox">
2147 <input type="checkbox" id="st-notifications" checked>
2148 <span>🔔 הצג התראות בדפדפן</span>
2149 </label>
2150
2151 <label class="st-checkbox">
2152 <input type="checkbox" id="st-sound" checked>
2153 <span>🔊 השמע צליל התראה</span>
2154 </label>
2155
2156 <label class="st-checkbox">
2157 <input type="checkbox" id="st-autocart">
2158 <span>🛒 הוסף לסל אוטומטית כשחוזר למלאי</span>
2159 </label>
2160
2161 <button class="st-btn" id="st-add">➕ הוסף מוצר למעקב</button>
2162 <button class="st-btn" id="st-quick-add" style="background:linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)">⚡ הוסף את כל המידות</button>
2163 <button class="st-btn" id="st-check">🔄 בדוק מלאי עכשיו</button>
2164 <button class="st-btn" id="st-test" style="background:linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)">🧪 בדיקת WhatsApp</button>
2165
2166 <div id="st-countdown" style="text-align:center;padding:12px;background:#2a2a2a;border-radius:8px;margin:10px 0;color:#25D366;font-weight:700;font-size:14px">
2167 ⏱️ בדיקה הבאה בעוד: <span id="st-countdown-text">--:--</span>
2168 </div>
2169
2170 <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px">
2171 <button class="st-btn st-btn-secondary" id="st-export" style="font-size:12px">💾 ייצוא</button>
2172 <button class="st-btn st-btn-secondary" id="st-import" style="font-size:12px">📥 ייבוא</button>
2173 </div>
2174
2175 <button class="st-btn st-btn-secondary st-btn-danger" id="st-clear" style="font-size:12px">🗑️ מחק הכל</button>
2176
2177 <div style="margin-top:20px">
2178 <h3 style="margin-bottom:12px">מוצרים במעקב:</h3>
2179
2180 <div style="background:#2a2a8a;border:1px solid #3b82f6;border-radius:8px;padding:12px;margin-bottom:12px">
2181 <div style="font-weight:700;margin-bottom:6px;color:#60a5fa">ℹ️ איך זה עובד?</div>
2182 <div style="font-size:12px;color:#93c5fd;line-height:1.5">
2183 • המערכת בודקת מלאי אוטומטית כל ${document.querySelector('#st-interval')?.value || 5} דקות<br>
2184 • <strong>חשוב:</strong> השאר דף אחד פתוח בדפדפן (לדוגמה: Zara, H&M)<br>
2185 • המערכת תשלח התראת WhatsApp כשמידה חוזרת למלאי<br>
2186 • <span id="st-status-text" style="color:#22c55e;font-weight:700">🟢 המערכת פעילה</span>
2187 </div>
2188 </div>
2189
2190 <div style="margin-bottom:12px">
2191 <input type="text" class="st-input" id="st-search" placeholder="🔍 חפש מוצר..." style="margin:0">
2192 </div>
2193
2194 <div style="display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap">
2195 <select class="st-input" id="st-sort" style="flex:1;margin:0">
2196 <option value="date-desc">📅 חדשים ראשון</option>
2197 <option value="date-asc">📅 ישנים ראשון</option>
2198 <option value="price-desc">💰 מחיר גבוה-נמוך</option>
2199 <option value="price-asc">💰 מחיר נמוך-גבוה</option>
2200 <option value="stock-desc">📊 הכי הרבה במלאי</option>
2201 <option value="stock-asc">📊 הכי פחות במלאי</option>
2202 </select>
2203 <select class="st-input" id="st-filter" style="flex:1;margin:0">
2204 <option value="all">🔍 הכל</option>
2205 <option value="in-stock">✅ במלאי בלבד</option>
2206 <option value="out-stock">❌ לא במלאי בלבד</option>
2207 <option value="partial">⚠️ חלקי במלאי</option>
2208 </select>
2209 </div>
2210
2211 <div id="st-list"></div>
2212 </div>
2213 </div>
2214 `;
2215 document.body.appendChild(panel);
2216
2217 // Event listeners
2218 fab.onclick = async () => {
2219 if (hasMoved) {
2220 hasMoved = false;
2221 return; // Don't open panel if dragging
2222 }
2223 const isOpen = panel.style.display === 'block';
2224 panel.style.display = isOpen ? 'none' : 'block';
2225 if (!isOpen) {
2226 await loadPrefsToUI();
2227 await renderList();
2228 await renderStats();
2229 }
2230 };
2231
2232 panel.querySelector('#st-close').onclick = () => {
2233 panel.style.display = 'none';
2234 };
2235
2236 panel.querySelector('#st-add').onclick = addCurrentProduct;
2237 panel.querySelector('#st-quick-add').onclick = quickAddAllSizes;
2238 panel.querySelector('#st-check').onclick = async () => {
2239 await checkAllStock();
2240 // Reset countdown after manual check
2241 const prefs = await getPrefs();
2242 const interval = (prefs.checkInterval || 5) * 60 * 1000;
2243 nextCheckTime = Date.now() + interval;
2244 };
2245 panel.querySelector('#st-test').onclick = async () => {
2246 const prefs = await getPrefs();
2247 const testMessage = '🧪 בדיקת מערכת\n\nזוהי הודעת בדיקה המערכת.\nאם קיבלת הודעה זו - המערכת עובדת תקין! ✅';
2248
2249 if (prefs.waPhone) {
2250 openWhatsApp(testMessage, prefs.waPhone);
2251 showToast('📱 פותח WhatsApp...', 'info');
2252 } else {
2253 showToast('⚠️ הזן מספר WhatsApp קודם', 'error');
2254 }
2255 };
2256 panel.querySelector('#st-export').onclick = exportData;
2257 panel.querySelector('#st-import').onclick = importData;
2258 panel.querySelector('#st-clear').onclick = clearAllData;
2259
2260 panel.querySelector('#st-phone').onchange = async (e) => {
2261 const prefs = await getPrefs();
2262 prefs.waPhone = e.target.value.trim();
2263 await savePrefs(prefs);
2264 showToast('✅ מספר WhatsApp נשמר', 'success');
2265 };
2266
2267 panel.querySelector('#st-interval').onchange = async (e) => {
2268 const prefs = await getPrefs();
2269 prefs.checkInterval = Math.max(1, parseInt(e.target.value) || 5);
2270 await savePrefs(prefs);
2271 showToast('✅ זמן ריענון עודכן', 'success');
2272 await startBackgroundCheck();
2273 };
2274
2275 panel.querySelector('#st-notifications').onchange = async (e) => {
2276 const prefs = await getPrefs();
2277 prefs.notifications = e.target.checked;
2278 await savePrefs(prefs);
2279 showToast(e.target.checked ? '🔔 התראות מופעלות' : '🔕 התראות כבויות', 'info');
2280 };
2281
2282 panel.querySelector('#st-sound').onchange = async (e) => {
2283 const prefs = await getPrefs();
2284 prefs.soundAlerts = e.target.checked;
2285 await savePrefs(prefs);
2286 if (e.target.checked) {
2287 playNotificationSound();
2288 showToast('🔊 צלילי התראה מופעלים', 'info');
2289 } else {
2290 showToast('🔇 צלילי התראה כבויים', 'info');
2291 }
2292 };
2293
2294 panel.querySelector('#st-autocart').onchange = async (e) => {
2295 const prefs = await getPrefs();
2296 prefs.autoAddToCart = e.target.checked;
2297 await savePrefs(prefs);
2298 showToast(e.target.checked ? '🛒 הוספה אוטומטית הופעלה' : '🛒 הוספה אוטומטית כובתה', 'info');
2299 };
2300
2301 // Search, filter, and sort event listeners
2302 const searchEl = panel.querySelector('#st-search');
2303 const sortEl = panel.querySelector('#st-sort');
2304 const filterEl = panel.querySelector('#st-filter');
2305
2306 if (searchEl) {
2307 searchEl.oninput = () => renderList();
2308 }
2309
2310 if (sortEl) {
2311 sortEl.onchange = () => renderList();
2312 }
2313
2314 if (filterEl) {
2315 filterEl.onchange = () => renderList();
2316 }
2317 }
2318
2319 // Toast notification
2320 function showToast(message, type = 'success') {
2321 const toast = document.createElement('div');
2322 toast.className = `st-toast ${type}`;
2323 toast.textContent = message;
2324 document.body.appendChild(toast);
2325
2326 setTimeout(() => {
2327 toast.style.transition = 'opacity 0.3s';
2328 toast.style.opacity = '0';
2329 setTimeout(() => toast.remove(), 300);
2330 }, 3500);
2331 }
2332
2333 // Load preferences to UI
2334 async function loadPrefsToUI() {
2335 const prefs = await getPrefs();
2336 const phoneEl = document.querySelector('#st-phone');
2337 const intervalEl = document.querySelector('#st-interval');
2338 const notificationsEl = document.querySelector('#st-notifications');
2339 const soundEl = document.querySelector('#st-sound');
2340 const autoCartEl = document.querySelector('#st-autocart');
2341
2342 if (phoneEl) phoneEl.value = prefs.waPhone || '';
2343 if (intervalEl) intervalEl.value = prefs.checkInterval || 5;
2344 if (notificationsEl) notificationsEl.checked = prefs.notifications !== false;
2345 if (soundEl) soundEl.checked = prefs.soundAlerts !== false;
2346 if (autoCartEl) autoCartEl.checked = !!prefs.autoAddToCart;
2347 }
2348
2349 // Render statistics
2350 async function renderStats() {
2351 const items = await getItems();
2352 const statsEl = document.querySelector('#st-stats');
2353
2354 if (!statsEl) return;
2355
2356 const totalItems = items.length;
2357 const totalSizes = items.reduce((sum, item) => sum + item.sizes.length, 0);
2358 const inStockSizes = items.reduce((sum, item) =>
2359 sum + item.sizes.filter(s => s.inStock).length, 0
2360 );
2361
2362 // Calculate total price
2363 let totalPrice = 0;
2364 let priceCount = 0;
2365 items.forEach(item => {
2366 const price = parsePrice(item.price);
2367 if (price) {
2368 totalPrice += price;
2369 priceCount++;
2370 }
2371 });
2372
2373 const avgPrice = priceCount > 0 ? (totalPrice / priceCount).toFixed(2) : 0;
2374
2375 statsEl.innerHTML = `
2376 <div class="st-stat">
2377 <div class="st-stat-value">${totalItems}</div>
2378 <div class="st-stat-label">מוצרים</div>
2379 </div>
2380 <div class="st-stat">
2381 <div class="st-stat-value">${totalSizes}</div>
2382 <div class="st-stat-label">מידות</div>
2383 </div>
2384 <div class="st-stat">
2385 <div class="st-stat-value">${inStockSizes}</div>
2386 <div class="st-stat-label">במלאי</div>
2387 </div>
2388 <div class="st-stat" style="grid-column: span 3">
2389 <div class="st-stat-value" style="color:#fbbf24">₪${totalPrice.toFixed(2)}</div>
2390 <div class="st-stat-label">סה"כ מחיר כל המוצרים | ממוצע: ₪${avgPrice}</div>
2391 </div>
2392 `;
2393 }
2394
2395 // Add current product
2396 async function addCurrentProduct() {
2397 const adapter = getAdapter();
2398 if (!adapter) {
2399 showToast('⚠️ אתר זה לא נתמך כרגע', 'error');
2400 return;
2401 }
2402
2403 const title = adapter.title();
2404 const price = adapter.price();
2405 const image = adapter.image();
2406 const url = location.href.split('#')[0].split('?')[0];
2407
2408 if (!title) {
2409 showToast('⚠️ לא הצלחתי לזהות את המוצר', 'error');
2410 return;
2411 }
2412
2413 showToast('⏳ מחפש מידות...', 'info');
2414
2415 const sizes = await adapter.getSizes();
2416
2417 if (!sizes || sizes.length === 0) {
2418 showToast('⚠️ לא נמצאו מידות', 'error');
2419 return;
2420 }
2421
2422 showSizeSelection(title, price || 'לא זמין', image, url, sizes);
2423 }
2424
2425 // Show size selection modal
2426 function showSizeSelection(title, price, image, url, sizes, editingItemId = null) {
2427 const overlay = document.createElement('div');
2428 overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.9);z-index:99999999;display:flex;align-items:center;justify-content:center';
2429
2430 const modal = document.createElement('div');
2431 modal.style.cssText = 'background:#1a1a1a;border:1px solid #333;border-radius:16px;padding:24px;max-width:500px;width:90%;max-height:80vh;overflow-y:auto';
2432
2433 modal.innerHTML = `
2434 <h2 style="margin:0 0 16px 0;color:#fff">${editingItemId ? '✏️ ערוך מידות' : '🧵 בחר מידות למעקב'}</h2>
2435 <p style="color:#999;margin-bottom:20px">${title}</p>
2436 <p style="color:#999;margin-bottom:20px">תקבל התראת WhatsApp כשהמידה חוזרת למלאי</p>
2437 <div id="size-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:10px;margin-bottom:20px"></div>
2438 <button class="st-btn" id="confirm-sizes">✅ ${editingItemId ? 'שמור שינויים' : 'התחל מעקב'}</button>
2439 <button class="st-btn st-btn-secondary" id="cancel-sizes" style="margin-top:0">❌ ביטול</button>
2440 `;
2441
2442 overlay.appendChild(modal);
2443 document.body.appendChild(overlay);
2444
2445 const sizeGrid = modal.querySelector('#size-grid');
2446 const selectedSizes = new Set();
2447
2448 sizes.forEach((size, idx) => {
2449 const btn = document.createElement('button');
2450 btn.style.cssText = `
2451 background: #2a2a2a;
2452 color: #fff;
2453 border: 2px solid ${size.inStock ? '#22c55e' : '#ef4444'};
2454 border-radius: 8px;
2455 padding: 12px;
2456 font-weight: 700;
2457 cursor: pointer;
2458 transition: all 0.2s;
2459 `;
2460 btn.textContent = `${size.label} ${size.inStock ? '✅' : '❌'}`;
2461
2462 btn.onclick = () => {
2463 if (selectedSizes.has(idx)) {
2464 selectedSizes.delete(idx);
2465 btn.style.background = '#2a2a2a';
2466 } else {
2467 selectedSizes.add(idx);
2468 btn.style.background = '#25D366';
2469 btn.style.color = '#000';
2470 }
2471 };
2472
2473 sizeGrid.appendChild(btn);
2474 });
2475
2476 modal.querySelector('#confirm-sizes').onclick = async () => {
2477 if (selectedSizes.size === 0) {
2478 showToast('⚠️ בחר לפחות מידה אחת', 'error');
2479 return;
2480 }
2481
2482 const items = await getItems();
2483
2484 const selectedSizeData = Array.from(selectedSizes).map(idx => ({
2485 label: sizes[idx].label,
2486 selector: buildSelector(sizes[idx].element),
2487 inStock: sizes[idx].inStock,
2488 lastChecked: Date.now()
2489 }));
2490
2491 if (editingItemId) {
2492 // Update existing item
2493 const itemIndex = items.findIndex(i => i.id === editingItemId);
2494 if (itemIndex !== -1) {
2495 items[itemIndex].sizes = selectedSizeData;
2496 items[itemIndex].price = price;
2497 showToast(`✅ המוצר עודכן עם ${selectedSizes.size} מידות!`, 'success');
2498 }
2499 } else {
2500 // Add new item
2501 items.push({
2502 id: Date.now().toString(),
2503 title,
2504 price,
2505 image,
2506 url,
2507 sizes: selectedSizeData,
2508 addedAt: Date.now()
2509 });
2510 showToast(`✅ נוספו ${selectedSizes.size} מידות למעקב!`, 'success');
2511 }
2512
2513 await saveItems(items);
2514 overlay.remove();
2515 await renderList();
2516 await renderStats();
2517 await updateFabBadge();
2518 };
2519
2520 modal.querySelector('#cancel-sizes').onclick = () => {
2521 overlay.remove();
2522 };
2523
2524 overlay.onclick = (e) => {
2525 if (e.target === overlay) overlay.remove();
2526 };
2527 }
2528
2529 // Render items list
2530 async function renderList() {
2531 const items = await getItems();
2532 const listEl = document.querySelector('#st-list');
2533
2534 if (!listEl) return;
2535
2536 if (items.length === 0) {
2537 listEl.innerHTML = '<p style="text-align:center;color:#999;padding:20px">אין מוצרים במעקב</p>';
2538 return;
2539 }
2540
2541 // Get search, sort, and filter values
2542 const searchQuery = document.querySelector('#st-search')?.value?.toLowerCase() || '';
2543 const sortValue = document.querySelector('#st-sort')?.value || 'date-desc';
2544 const filterValue = document.querySelector('#st-filter')?.value || 'all';
2545
2546 // Filter items
2547 let filteredItems = [...items];
2548
2549 // Apply search filter
2550 if (searchQuery) {
2551 filteredItems = filteredItems.filter(item =>
2552 item.title.toLowerCase().includes(searchQuery) ||
2553 item.url.toLowerCase().includes(searchQuery)
2554 );
2555 }
2556
2557 // Apply stock filter
2558 if (filterValue === 'in-stock') {
2559 filteredItems = filteredItems.filter(item =>
2560 item.sizes.every(s => s.inStock)
2561 );
2562 } else if (filterValue === 'out-stock') {
2563 filteredItems = filteredItems.filter(item =>
2564 item.sizes.every(s => !s.inStock)
2565 );
2566 } else if (filterValue === 'partial') {
2567 filteredItems = filteredItems.filter(item =>
2568 item.sizes.some(s => s.inStock) && item.sizes.some(s => !s.inStock)
2569 );
2570 }
2571
2572 // Apply sorting
2573 filteredItems.sort((a, b) => {
2574 if (sortValue === 'date-desc') {
2575 return b.addedAt - a.addedAt;
2576 } else if (sortValue === 'date-asc') {
2577 return a.addedAt - b.addedAt;
2578 } else if (sortValue === 'price-desc') {
2579 const priceA = parsePrice(a.price) || 0;
2580 const priceB = parsePrice(b.price) || 0;
2581 return priceB - priceA;
2582 } else if (sortValue === 'price-asc') {
2583 const priceA = parsePrice(a.price) || 0;
2584 const priceB = parsePrice(b.price) || 0;
2585 return priceA - priceB;
2586 } else if (sortValue === 'stock-desc') {
2587 const stockA = a.sizes.filter(s => s.inStock).length;
2588 const stockB = b.sizes.filter(s => s.inStock).length;
2589 return stockB - stockA;
2590 } else if (sortValue === 'stock-asc') {
2591 const stockA = a.sizes.filter(s => s.inStock).length;
2592 const stockB = b.sizes.filter(s => s.inStock).length;
2593 return stockA - stockB;
2594 }
2595 return 0;
2596 });
2597
2598 // Show no results message if filtered list is empty
2599 if (filteredItems.length === 0) {
2600 listEl.innerHTML = '<p style="text-align:center;color:#999;padding:20px">לא נמצאו מוצרים מתאימים</p>';
2601 return;
2602 }
2603
2604 listEl.innerHTML = filteredItems.map(item => {
2605 const inStockCount = item.sizes.filter(s => s.inStock).length;
2606 const totalCount = item.sizes.length;
2607 const siteName = getSiteName(item.url);
2608
2609 return `
2610 <div class="st-item" data-item-id="${item.id}">
2611 <div style="display:flex;gap:12px;margin-bottom:12px">
2612 ${item.image ? `<img src="${item.image}" style="width:60px;height:60px;object-fit:cover;border-radius:8px">` : ''}
2613 <div style="flex:1">
2614 <div style="font-weight:700;margin-bottom:4px">${item.title}</div>
2615 <div style="color:#999;font-size:11px;margin-bottom:4px">🏪 ${siteName}</div>
2616 <div style="color:#999;font-size:12px">💰 מחיר: ${item.price}</div>
2617 <div style="color:#999;font-size:12px">📊 מלאי: ${inStockCount}/${totalCount} מידות</div>
2618 <div style="color:#999;font-size:12px">📅 נוסף: ${formatTimeAgo(item.addedAt)}</div>
2619 ${item.sizes[0]?.lastChecked ? `<div style="color:#999;font-size:12px">🔄 נבדק: ${formatTimeAgo(item.sizes[0].lastChecked)}</div>` : ''}
2620 </div>
2621 </div>
2622 <div style="margin:8px 0">
2623 ${item.sizes.map(s => `
2624 <span class="st-size ${s.inStock ? 'in-stock' : 'out-of-stock'}" title="לחץ לפתיחת המוצר" onclick="window.open('${item.url}', '_blank')">
2625 ${s.label} ${s.inStock ? '✅' : '❌'}
2626 </span>
2627 `).join('')}
2628 </div>
2629 <div class="st-button-grid">
2630 <button class="st-btn st-btn-secondary st-action-btn" data-action="check" data-id="${item.id}" style="padding:10px 8px;font-size:11px">🔍 בדוק</button>
2631 <button class="st-btn st-btn-secondary st-action-btn" data-action="open" data-url="${item.url}" style="padding:10px 8px;font-size:11px">🔗 פתח</button>
2632 <button class="st-btn st-btn-secondary st-action-btn" data-action="edit" data-id="${item.id}" style="padding:10px 8px;font-size:11px">✏️ ערוך</button>
2633 <button class="st-btn st-btn-secondary st-action-btn" data-action="copy" data-url="${item.url}" style="padding:10px 8px;font-size:11px">📋 העתק</button>
2634 <button class="st-btn st-btn-secondary st-action-btn" data-action="remove" data-id="${item.id}" style="padding:10px 8px;font-size:11px">🗑️ מחק</button>
2635 </div>
2636 </div>
2637 `;}).join('');
2638
2639 // Add event listeners to buttons using event delegation
2640 listEl.querySelectorAll('.st-action-btn').forEach(btn => {
2641 btn.addEventListener('click', async function(e) {
2642 e.preventDefault();
2643 e.stopPropagation();
2644
2645 const action = this.getAttribute('data-action');
2646 const id = this.getAttribute('data-id');
2647 const url = this.getAttribute('data-url');
2648
2649 console.log('[Stock Tracker] Button clicked:', action, 'id:', id, 'url:', url);
2650
2651 if (action === 'check') {
2652 await checkSingleItem(id);
2653 } else if (action === 'open') {
2654 window.open(url, '_blank');
2655 } else if (action === 'edit') {
2656 await editItem(id);
2657 } else if (action === 'copy') {
2658 await copyProductLink(url);
2659 } else if (action === 'remove') {
2660 await removeItem(id);
2661 }
2662 });
2663 });
2664 }
2665
2666 // Remove item
2667 async function removeItem(id) {
2668 console.log('[Stock Tracker] Removing item:', id);
2669
2670 if (!confirm('האם אתה בטוח שברצונך למחוק מוצר זה?')) return;
2671
2672 const items = await getItems();
2673 const filtered = items.filter(item => item.id !== id);
2674
2675 console.log('[Stock Tracker] Items before:', items.length, 'after:', filtered.length);
2676
2677 await saveItems(filtered);
2678 await renderList();
2679 await renderStats();
2680 await updateFabBadge();
2681 showToast('🗑️ מוצר נמחק בהצלחה', 'success');
2682 }
2683
2684 // Edit item
2685 async function editItem(id) {
2686 console.log('[Stock Tracker] Editing item:', id);
2687
2688 const items = await getItems();
2689 const item = items.find(i => i.id === id);
2690 if (!item) {
2691 showToast('⚠️ מוצר לא נמצא', 'error');
2692 return;
2693 }
2694
2695 showToast('🔗 פותח את העמוד לעריכה...', 'info');
2696 window.open(item.url, '_blank');
2697 }
2698
2699 // Start background checking
2700 let checkInterval = null;
2701 async function startBackgroundCheck() {
2702 if (checkInterval) clearInterval(checkInterval);
2703 if (countdownInterval) clearInterval(countdownInterval);
2704
2705 const prefs = await getPrefs();
2706 const interval = (prefs.checkInterval || 5) * 60 * 1000;
2707
2708 // Set next check time
2709 nextCheckTime = Date.now() + interval;
2710
2711 // Start countdown display
2712 countdownInterval = setInterval(updateCountdown, 1000);
2713 updateCountdown();
2714
2715 checkInterval = setInterval(() => {
2716 nextCheckTime = Date.now() + interval;
2717 checkAllStock();
2718 }, interval);
2719
2720 console.log('[Stock Tracker] Background check started. Interval:', interval / 60000, 'minutes');
2721 }
2722
2723 // Update countdown display
2724 function updateCountdown() {
2725 const countdownEl = document.querySelector('#st-countdown-text');
2726 if (!countdownEl || !nextCheckTime) return;
2727
2728 const now = Date.now();
2729 const diff = nextCheckTime - now;
2730
2731 if (diff <= 0) {
2732 countdownEl.textContent = '00:00';
2733 return;
2734 }
2735
2736 const minutes = Math.floor(diff / 60000);
2737 const seconds = Math.floor((diff % 60000) / 1000);
2738
2739 countdownEl.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
2740 }
2741
2742 // Initialize
2743 async function init() {
2744 console.log('[Stock Tracker] Initializing...');
2745
2746 // Check if site is blocked
2747 if (isBlockedSite()) {
2748 console.log('[Stock Tracker] Blocked site detected, not initializing');
2749 return;
2750 }
2751
2752 // Always check if we have tracked items - if yes, run background checks
2753 const items = await getItems();
2754 const isEcommerce = isEcommerceSite();
2755
2756 // Fix old selectors that include status classes
2757 if (items.length > 0) {
2758 let needsSave = false;
2759 items.forEach(item => {
2760 item.sizes.forEach(size => {
2761 if (typeof size.selector === 'string' && (size.selector.includes('--disabled') || size.selector.includes('--unavailable') || size.selector.includes('--enabled'))) {
2762 console.log('[Stock Tracker] Fixing old selector for size:', size.label);
2763 // Remove status classes from selector
2764 size.selector = size.selector
2765 .replace(/\.size-selector-sizes-size--disabled/g, '')
2766 .replace(/\.size-selector-sizes-size--unavailable/g, '')
2767 .replace(/\.size-selector-sizes-size--enabled/g, '')
2768 .replace(/\.is-disabled/g, '')
2769 .replace(/\.is-unavailable/g, '')
2770 .replace(/\.disabled/g, '')
2771 .replace(/\.unavailable/g, '')
2772 .replace(/\.enabled/g, '');
2773 needsSave = true;
2774 }
2775 });
2776 });
2777
2778 if (needsSave) {
2779 await saveItems(items);
2780 console.log('[Stock Tracker] Fixed old selectors, saved updated items');
2781 }
2782 }
2783
2784 // If we have items, always initialize (even on non-ecommerce sites)
2785 // This ensures background checks run everywhere
2786 if (items.length > 0) {
2787 console.log('[Stock Tracker] Found', items.length, 'tracked items, initializing...');
2788 } else if (!isEcommerce) {
2789 console.log('[Stock Tracker] Not an e-commerce site and no tracked items, not initializing');
2790 return;
2791 }
2792
2793 if (!document.body) {
2794 await new Promise(resolve => {
2795 if (document.readyState === 'loading') {
2796 document.addEventListener('DOMContentLoaded', resolve);
2797 } else {
2798 resolve();
2799 }
2800 });
2801 }
2802
2803 createUI();
2804
2805 // Always start background checks if we have items
2806 if (items.length > 0) {
2807 await startBackgroundCheck();
2808 console.log('[Stock Tracker] Background checks started for', items.length, 'items');
2809 }
2810
2811 await updateFabBadge();
2812
2813 if (location.host.includes('web.whatsapp.com')) {
2814 autoSendWhatsApp();
2815 }
2816
2817 // Run auto-cart if requested via URL parameters
2818 runAutoCartIfRequested();
2819
2820 // Global API
2821 window.stockTracker = {
2822 removeItem,
2823 editItem,
2824 checkAllStock,
2825 exportData,
2826 importData,
2827 copyLink: copyProductLink
2828 };
2829
2830 console.log('[Stock Tracker] Ready!');
2831 }
2832
2833 init().catch(err => console.error('[Stock Tracker] Init error:', err));
2834
2835})();