diff --git a/extension/content.js b/extension/content.js index 6c75f44..caed12f 100644 --- a/extension/content.js +++ b/extension/content.js @@ -1,7 +1,7 @@ // Facebook Post Tracker Extension // Uses API_BASE_URL from config.js -const EXTENSION_VERSION = '1.2.1'; +const EXTENSION_VERSION = '1.2.2'; const PROCESSED_ATTR = 'data-fb-tracker-processed'; const PENDING_ATTR = 'data-fb-tracker-pending'; const DIALOG_ROOT_SELECTOR = '[role="dialog"], [data-pagelet*="Modal"], [data-pagelet="StoriesRecentStoriesFeedSection"]'; @@ -1019,6 +1019,7 @@ const aiCredentialCache = { const AI_SETTINGS_CACHE_TTL = 30 * 1000; const AI_SETTINGS_SYNC_KEY = 'fb_tracker_ai_settings_sync'; const AI_AVAILABILITY_AUTO_REFRESH_INTERVAL_MS = 30 * 1000; +const AI_REACTIVATION_COUNTDOWN_THRESHOLD_MS = 5 * 60 * 1000; const aiSettingsCache = { data: null, timestamp: 0, @@ -6852,6 +6853,7 @@ async function addAICommentButton(container, postElement) { let notePreviewElement = null; let noteClearButton = null; let rateLimitRefreshPromise = null; + let blockedCountdownIntervalId = null; const truncateNoteForPreview = (note) => { if (!note) { @@ -6887,6 +6889,81 @@ async function addAICommentButton(container, postElement) { showToast(`⏳ ${status.profile_name || 'Profil'}: ${formatAIAutoCommentRateLimitReason(status)}.${untilText}`.trim(), 'info'); }; + const clearBlockedCountdown = () => { + if (blockedCountdownIntervalId) { + window.clearInterval(blockedCountdownIntervalId); + blockedCountdownIntervalId = null; + } + }; + + const getBlockedCountdownRemainingMs = (status) => { + if (!status || !status.blocked_until) { + return null; + } + const blockedUntilMs = new Date(status.blocked_until).getTime(); + if (!Number.isFinite(blockedUntilMs)) { + return null; + } + return blockedUntilMs - Date.now(); + }; + + const formatBlockedCountdown = (remainingMs) => { + const totalSeconds = Math.max(0, Math.ceil(remainingMs / 1000)); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + if (hours > 0) { + return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; + } + return `${minutes}:${String(seconds).padStart(2, '0')}`; + }; + + const renderBlockedCountdownText = (status) => { + const remainingMs = getBlockedCountdownRemainingMs(status); + if (remainingMs === null || remainingMs <= 0 || remainingMs > AI_REACTIVATION_COUNTDOWN_THRESHOLD_MS) { + return false; + } + button.textContent = `${button.dataset.aiOriginalText || baseButtonText} 🔒 ${formatBlockedCountdown(remainingMs)}`; + return true; + }; + + const scheduleBlockedCountdown = (status) => { + clearBlockedCountdown(); + if (!status || !status.blocked) { + return; + } + if ((button.dataset.aiState || 'idle') !== 'idle' || button._aiContext) { + return; + } + + const updateCountdown = () => { + if (!wrapper.isConnected) { + clearBlockedCountdown(); + return; + } + if ((button.dataset.aiState || 'idle') !== 'idle' || button._aiContext) { + clearBlockedCountdown(); + return; + } + const remainingMs = getBlockedCountdownRemainingMs(status); + if (remainingMs !== null && remainingMs <= 0) { + clearBlockedCountdown(); + void refreshAvailabilityState(true); + return; + } + if (!renderBlockedCountdownText(status)) { + clearBlockedCountdown(); + } + }; + + if (!renderBlockedCountdownText(status)) { + return; + } + + blockedCountdownIntervalId = window.setInterval(updateCountdown, 1000); + }; + const applyAvailabilityState = (status = null, options = {}) => { const { preserveText = false } = options; const blocked = Boolean(status && status.blocked); @@ -6894,6 +6971,7 @@ async function addAICommentButton(container, postElement) { button.dataset.aiAvailability = blocked ? 'blocked' : 'available'; if (blocked) { + clearBlockedCountdown(); wrapper.style.opacity = '0.72'; wrapper.style.boxShadow = baseWrapperShadow; wrapper.style.transform = 'translateY(0)'; @@ -6904,10 +6982,15 @@ async function addAICommentButton(container, postElement) { button.title = buildBlockedButtonTitle(status); dropdownButton.title = buildBlockedButtonTitle(status); dropdownButton.setAttribute('aria-label', buildBlockedButtonTitle(status)); - if (!preserveText && (button.dataset.aiState || 'idle') === 'idle' && !button._aiContext) { - button.textContent = `${button.dataset.aiOriginalText || baseButtonText} 🔒`; + if ((button.dataset.aiState || 'idle') === 'idle' && !button._aiContext) { + const countdownVisible = renderBlockedCountdownText(status); + if (!countdownVisible && !preserveText) { + button.textContent = `${button.dataset.aiOriginalText || baseButtonText} 🔒`; + } + scheduleBlockedCountdown(status); } } else { + clearBlockedCountdown(); wrapper.style.opacity = '1'; wrapper.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; button.style.cursor = 'pointer'; @@ -6959,6 +7042,7 @@ async function addAICommentButton(container, postElement) { const handleSharedAISettingsUpdate = async (settings) => { if (!wrapper.isConnected) { + clearBlockedCountdown(); aiAvailabilitySubscribers.delete(handleSharedAISettingsUpdate); return; } @@ -6980,6 +7064,7 @@ async function addAICommentButton(container, postElement) { const handleAutoRefreshAvailability = ({ forceRefresh = false } = {}) => { if (!wrapper.isConnected) { + clearBlockedCountdown(); aiAvailabilitySubscribers.delete(handleSharedAISettingsUpdate); aiAvailabilityRefreshSubscribers.delete(handleAutoRefreshAvailability); return; diff --git a/extension/manifest.json b/extension/manifest.json index 2dac814..3f79e45 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Facebook Post Tracker", - "version": "1.2.1", + "version": "1.2.2", "description": "Track Facebook posts across multiple profiles", "permissions": [ "storage",