Select Blog

Select a blog to post to.

StylerPro API Blog Auto Posting Special Edition

by Digital Nomad YouTuber

Dino AI Unlimited Version

Automatic image generation included (Free images, Pollinations token)

Gemini 2.5 Flash Lite with Search:
AI automatically searches for latest information
Completely free (no API key needed)
Integrated search functionality for accurate content

Where to find configuration information?

Google OAuth Client ID:
Enable `Blogger API` in Google Cloud Console, then go to 'Credentials' > '+ Create' > 'OAuth client ID' (Type: Web application). **Must add** the URL where you'll run this app (e.g., `http://localhost:8080`) to **'Authorized JavaScript origins'**.

Pollinations Token:
Login with GitHub at Pollinations Auth to receive your token. Used for both text and image generation, unlimited usage available.

2. Introduction with HOOK - Tighter line spacing (2-3 sentences per paragraph)

Start with surprising fact or relatable situation that hooks readers. Second sentence that amplifies curiosity.

Third sentence building on the hook with valuable insight. Fourth sentence creating more intrigue. Fifth sentence if closely related.

Final two sentences that naturally lead into main content without announcements.

3. Section Titles (h2) - Clean and Readable Style with Box Design

First Section Title

4. Main Content - TIGHT line break rule (2-3 sentences max)

First sentence with important information. Second sentence expanding the point.

Third sentence introducing new aspect. Fourth sentence with supporting details. Fifth if directly connected.

Sixth sentence with examples. Seventh sentence concluding.

Eighth and ninth sentences for final thoughts.

Information about Official Service Name with link on first mention. Related information continues.

When mentioning Official Service Name again, no link needed. More specific details here.

Additional relevant information. Closing thought for this section.

💡 Key Point: First important sentence. Second sentence with key insight.

Third and fourth sentences if needed to complete the point.

5. Clean Table Design - Use only 1-2 tables in ENTIRE article

📊 Comparison Table

Item 1 Item 2
Clear content Direct information
Well-spaced data Readable content
6. FAQ Section (Section 7) - Q&A with tight line breaks

❓ Frequently Asked Questions

Q1. Natural question without meta-references?

A1. First and second sentences of answer.

Third sentence if needed. Fourth sentence to complete the answer.

7. Disclaimer and Summary Box

⚠️ Disclaimer

First and second disclaimer sentences.

Third sentence about professional advice. Fourth sentence if needed.

📌 Summary

• Point 1 without parentheses
• Point 2 with clear information
• Point 3 direct and concise
• Point 4 valuable takeaway
• Point 5 actionable insight

ABSOLUTE REQUIREMENTS: - NO TAGS IN BODY: Never display tags/labels in the article content (only for metadata) - TABLE OF CONTENTS: EXACTLY 7 sections (6 main + 1 FAQ) - TIGHT LINE SPACING: Maximum 2-3 sentences before

(prefer 2) - Pattern: Sentence1. Sentence2.

OR Sentence1. Sentence2. Sentence3.

- INTRODUCTION MUST have engaging HOOK (question, fact, story, statistic) - NEVER use AI phrases: "This article", "In this post", "We will explore", "Let's examine", "I'll explain" - NEVER end introduction with "we will explore" or similar announcements - Create natural flow and curiosity, not article descriptions - NEVER write more than 3 consecutive sentences without line break - NEVER use parentheses () for any purpose - External links: Add ONLY on first mention of official websites - After first link, use plain text for subsequent mentions - Google E-E-A-T and Naver D.I.A.+ optimization - Tables: Use only 1-2 h3 tables in ENTIRE article - Introduction: 300-500 characters with strong HOOK - Each section: 600-1000 characters - Total article: maximum 8,000 characters - FAQ: exactly 6 Q&A pairs - Summary: maximum 5 lines - Labels/Tags: exactly 4 SEO keywords (but don't display them) - Table text: black (#000000) Please write with engaging hooks and natural flow, avoiding all AI-sounding phrases, with exactly 7 sections and tight line spacing.`; // Language selection setup function setupLanguageSelection() { const languageOptions = document.querySelectorAll('.language-option'); languageOptions.forEach(option => { option.addEventListener('click', function() { // Remove selected class from all options languageOptions.forEach(opt => opt.classList.remove('selected')); // Add selected class to clicked option this.classList.add('selected'); // Update selected language selectedLanguage = this.dataset.language; // Update display const selectedLangDisplay = document.getElementById('selected-language-display'); if (selectedLangDisplay) { selectedLangDisplay.textContent = languageConfig[selectedLanguage].name; } // Save preference localStorage.setItem('preferredLanguage', selectedLanguage); addLog(`✅ Language changed to: ${languageConfig[selectedLanguage].name}`, 'info'); }); }); // Load saved preference const savedLanguage = localStorage.getItem('preferredLanguage'); if (savedLanguage && languageConfig[savedLanguage]) { selectedLanguage = savedLanguage; // Update UI languageOptions.forEach(opt => { opt.classList.remove('selected'); if (opt.dataset.language === savedLanguage) { opt.classList.add('selected'); } }); const selectedLangDisplay = document.getElementById('selected-language-display'); if (selectedLangDisplay) { selectedLangDisplay.textContent = languageConfig[selectedLanguage].name; } } } // Multi-blog RSS related functions (continue from previous code...) window.addBlogInput = function() { if (currentBlogCount >= 4) { showStatusMessage('Maximum 4 blogs allowed.', 'error'); return; } currentBlogCount++; const container = document.getElementById('blog-url-inputs-container'); const inputGroup = document.createElement('div'); inputGroup.className = 'multi-blog-input-group'; inputGroup.innerHTML = ` `; container.appendChild(inputGroup); lucide.createIcons(); // Show remove button for first input const firstRemoveBtn = container.querySelector('.multi-blog-input-group:first-child .remove-blog-url-btn'); if (firstRemoveBtn && currentBlogCount > 1) { firstRemoveBtn.classList.remove('hidden'); } // Hide add button at 4 if (currentBlogCount >= 4) { document.getElementById('add-blog-url-btn').style.display = 'none'; } } window.removeBlogInput = function(index) { const container = document.getElementById('blog-url-inputs-container'); const inputGroups = container.querySelectorAll('.multi-blog-input-group'); if (inputGroups.length <= 1) { showStatusMessage('At least 1 blog must be maintained.', 'error'); return; } // Remove the input inputGroups.forEach((group, i) => { const input = group.querySelector('.multi-blog-input'); if (input && parseInt(input.dataset.blogIndex) === index) { group.remove(); } }); currentBlogCount--; // Reindex const newInputGroups = container.querySelectorAll('.multi-blog-input-group'); newInputGroups.forEach((group, i) => { const input = group.querySelector('.multi-blog-input'); const removeBtn = group.querySelector('.remove-blog-url-btn'); if (input) { input.dataset.blogIndex = i; input.placeholder = `https://example${i + 1}.blogspot.com`; } if (removeBtn) { removeBtn.setAttribute('onclick', `removeBlogInput(${i})`); } }); // Hide remove button when only 1 left if (newInputGroups.length === 1) { const lastRemoveBtn = newInputGroups[0].querySelector('.remove-blog-url-btn'); if (lastRemoveBtn) { lastRemoveBtn.classList.add('hidden'); } } // Show add button again if (currentBlogCount < 4) { document.getElementById('add-blog-url-btn').style.display = 'inline-flex'; } } // Modified multi RSS fetch function async function handleFetchMultiRss() { const blogInputs = document.querySelectorAll('.multi-blog-input'); const blogUrls = []; blogInputs.forEach(input => { const url = normalizeUrl(input.value.trim()); if (url) { blogUrls.push({ url, index: parseInt(input.dataset.blogIndex) }); } }); if (blogUrls.length === 0) { showStatusMessage('Please enter at least one blog address.', 'error'); return; } const fetchButton = document.getElementById('fetch-multi-rss-button'); fetchButton.disabled = true; fetchButton.innerHTML = ' Loading...'; multiBlogData = []; multiBlogPosts = {}; selectedMultiBlogPosts = []; totalSelectedPosts = 0; try { showStatusMessage(`Fetching RSS feeds from ${blogUrls.length} blog(s)...`, 'info'); const fetchPromises = blogUrls.map(async (blogInfo) => { const rssUrls = generateRssUrls(blogInfo.url); let posts = []; for (const rssUrl of rssUrls) { for (const proxyUrl of corsProxies) { try { const rssData = await fetchRssWithProxy(rssUrl, proxyUrl); posts = parseRss(rssData); if (posts.length > 0) break; } catch (error) { continue; } } if (posts.length > 0) break; } return { url: blogInfo.url, index: blogInfo.index, posts: posts }; }); const results = await Promise.all(fetchPromises); let totalPosts = 0; results.forEach(result => { if (result.posts.length > 0) { multiBlogData.push(result); multiBlogPosts[result.index] = result.posts; totalPosts += result.posts.length; } }); if (multiBlogData.length === 0) { throw new Error('Could not fetch RSS feeds.'); } showStatusMessage(` Successfully fetched ${totalPosts} posts from ${multiBlogData.length} blog(s)!`, 'success'); displayMultiBlogPosts(); } catch (error) { console.error('Multi RSS fetch failed:', error); showStatusMessage('Could not fetch RSS feeds. Please check the blog addresses.', 'error'); } finally { fetchButton.disabled = false; fetchButton.innerHTML = ' Fetch RSS from All Blogs'; lucide.createIcons(); } } // Modified multi-blog posts display function function displayMultiBlogPosts() { const container = document.getElementById('multi-blog-rss-container'); const sectionsContainer = document.getElementById('multi-blog-sections'); container.classList.remove('hidden'); sectionsContainer.innerHTML = ''; multiBlogData.forEach((blogData, blogIndex) => { const section = document.createElement('div'); section.className = 'blog-rss-section'; section.dataset.blogUrl = blogData.url; section.dataset.blogIndex = blogData.index; const headerHtml = `
${blogData.url.replace(/https?:\/\//, '').substring(0, 30)}... ${blogData.posts.length}
`; const postsHtml = `
${blogData.posts.slice(0, 25).map((post, postIndex) => { const dateStr = post.pubDate ? new Date(post.pubDate).toLocaleDateString('en-US') : ''; return `
`; }).join('')}
`; section.innerHTML = headerHtml + postsHtml; sectionsContainer.appendChild(section); }); // Add checkbox event listeners sectionsContainer.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { checkbox.addEventListener('change', handleMultiBlogPostSelection); }); updateMultiSelectedPostsCount(); } // Completely new selection logic function handleMultiBlogPostSelection(e) { const checkbox = e.target; if (checkbox.checked) { // Cannot add more if already 4 selected if (totalSelectedPosts >= 4) { checkbox.checked = false; showStatusMessage('Maximum 4 posts can be selected.', 'warn'); return; } const postItem = checkbox.closest('.multi-rss-post-item'); postItem.classList.add('selected'); // Add selected post const postData = { blogUrl: checkbox.dataset.blogUrl, blogIndex: checkbox.dataset.blogIndex, title: checkbox.dataset.title, link: checkbox.dataset.link }; selectedMultiBlogPosts.push(postData); totalSelectedPosts++; } else { const postItem = checkbox.closest('.multi-rss-post-item'); postItem.classList.remove('selected'); // Deselect selectedMultiBlogPosts = selectedMultiBlogPosts.filter(p => !(p.link === checkbox.dataset.link && p.blogUrl === checkbox.dataset.blogUrl) ); totalSelectedPosts--; } updateMultiSelectedPostsCount(); updateSelectedPostsSummary(); // When 4 selected if (totalSelectedPosts === 4) { selectedRssPosts = selectedMultiBlogPosts.map(p => ({ title: p.title, link: p.link })); generateMainTitle(); generateButtonTexts(); document.getElementById('internal-link-config').classList.remove('hidden'); document.getElementById('internal-char-count-info').classList.remove('hidden'); setupTitleAndButtonEditing(); // Show selection info const selectionInfo = document.getElementById('multi-blog-selection-info'); if (selectionInfo) { const blogCounts = {}; selectedMultiBlogPosts.forEach(post => { const shortUrl = post.blogUrl.replace(/https?:\/\//, '').substring(0, 20); blogCounts[shortUrl] = (blogCounts[shortUrl] || 0) + 1; }); const summaryText = Object.entries(blogCounts) .map(([url, count]) => `${url}... (${count})`) .join(', '); selectionInfo.innerHTML = ` Total 4 posts selected!
Selected by blog: ${summaryText} `; selectionInfo.classList.remove('hidden'); } } else { document.getElementById('internal-link-config').classList.add('hidden'); document.getElementById('internal-char-count-info').classList.add('hidden'); document.getElementById('multi-blog-selection-info').classList.add('hidden'); } // Update blog section highlights updateBlogSectionHighlights(); } // Blog section highlight update function function updateBlogSectionHighlights() { const blogSections = document.querySelectorAll('.blog-rss-section'); blogSections.forEach(section => { const hasSelection = section.querySelector('input[type="checkbox"]:checked'); if (hasSelection) { section.classList.add('has-posts'); } else { section.classList.remove('has-posts'); } }); } // Selected posts summary display function function updateSelectedPostsSummary() { const summaryEl = document.getElementById('selected-posts-summary'); if (!summaryEl) return; if (totalSelectedPosts > 0) { const summaryHtml = ` Selected Posts (${totalSelectedPosts}/4):
${selectedMultiBlogPosts.map((post, idx) => `${idx + 1}. ${post.title.substring(0, 30)}...` ).join('
')} `; summaryEl.innerHTML = summaryHtml; summaryEl.classList.remove('hidden'); } else { summaryEl.classList.add('hidden'); } } // Modified count update function function updateMultiSelectedPostsCount() { const countElement = document.getElementById('multi-selected-posts-count'); if (countElement) { countElement.textContent = `${totalSelectedPosts}/4 selected`; countElement.className = totalSelectedPosts === 4 ? 'text-sm text-green-600 font-bold' : 'text-sm text-gray-600'; } // Status message update const statusEl = document.getElementById('multi-blog-status'); if (statusEl) { if (totalSelectedPosts === 0) { statusEl.innerHTML = 'Please select a total of 4 posts regardless of blog count.'; statusEl.classList.remove('hidden'); } else if (totalSelectedPosts < 4) { const remaining = 4 - totalSelectedPosts; statusEl.innerHTML = `Please select ${remaining} more. (Currently ${totalSelectedPosts}/4)`; statusEl.classList.remove('hidden'); } else { statusEl.classList.add('hidden'); } } } // Blog URL copy function window.copyBlogUrl = function() { const blogUrlText = document.getElementById('blog-url-text'); const copyBtn = document.getElementById('copy-url-btn'); if (blogUrlText && selectedBlogUrl) { navigator.clipboard.writeText(selectedBlogUrl).then(() => { const originalHtml = copyBtn.innerHTML; copyBtn.innerHTML = ''; copyBtn.style.backgroundColor = 'rgba(34, 197, 94, 0.3)'; setTimeout(() => { copyBtn.innerHTML = originalHtml; copyBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.2)'; lucide.createIcons(); }, 2000); showStatusMessage('Blog address copied to clipboard!', 'success'); }).catch(err => { showStatusMessage('Copy failed: ' + err.message, 'error'); }); } } // Title and button editing related functions - modified version function setupTitleAndButtonEditing() { // Remove existing event listeners const oldEditMainTitleBtn = document.getElementById('edit-main-title-btn'); const oldEditButtonsBtn = document.getElementById('edit-buttons-btn'); if (oldEditMainTitleBtn) { const newEditMainTitleBtn = oldEditMainTitleBtn.cloneNode(true); oldEditMainTitleBtn.parentNode.replaceChild(newEditMainTitleBtn, oldEditMainTitleBtn); } if (oldEditButtonsBtn) { const newEditButtonsBtn = oldEditButtonsBtn.cloneNode(true); oldEditButtonsBtn.parentNode.replaceChild(newEditButtonsBtn, oldEditButtonsBtn); } // Register new event listeners const editMainTitleBtn = document.getElementById('edit-main-title-btn'); const editButtonsBtn = document.getElementById('edit-buttons-btn'); const mainTitleInput = document.getElementById('generated-main-title'); const buttonEditContainer = document.getElementById('button-edit-container'); if (editMainTitleBtn && mainTitleInput) { editMainTitleBtn.onclick = function(e) { e.preventDefault(); if (!isEditingMainTitle) { // Enable edit mode mainTitleInput.readOnly = false; mainTitleInput.classList.remove('bg-gray-100'); mainTitleInput.classList.add('bg-white', 'focus:ring-2', 'focus:ring-blue-500'); mainTitleInput.focus(); editMainTitleBtn.innerHTML = ' Save'; isEditingMainTitle = true; updateMainTitleCharCount(); } else { // Save mode mainTitleInput.readOnly = true; mainTitleInput.classList.add('bg-gray-100'); mainTitleInput.classList.remove('bg-white', 'focus:ring-2', 'focus:ring-blue-500'); editMainTitleBtn.innerHTML = ' Edit'; isEditingMainTitle = false; generatedMainTitle = mainTitleInput.value; showStatusMessage('Title saved.', 'success'); } lucide.createIcons(); }; } if (editButtonsBtn && buttonEditContainer) { editButtonsBtn.onclick = function(e) { e.preventDefault(); if (!isEditingButtons) { // Enable edit mode buttonEditContainer.classList.remove('hidden'); editButtonsBtn.innerHTML = ' Save'; isEditingButtons = true; // Display current button text in input fields generatedButtonTexts.forEach((text, idx) => { const input = document.getElementById(`button-text-${idx + 1}`); if (input) { input.value = text; updateButtonCharCount(idx + 1); } }); } else { // Save mode buttonEditContainer.classList.add('hidden'); editButtonsBtn.innerHTML = ' Edit'; isEditingButtons = false; // Save modified button text for (let i = 1; i <= 4; i++) { const input = document.getElementById(`button-text-${i}`); if (input && generatedButtonTexts[i - 1] !== undefined) { generatedButtonTexts[i - 1] = input.value; } } // Update preview updateButtonPreview(); showStatusMessage('Button text saved.', 'success'); } lucide.createIcons(); }; } // Title input character count update if (mainTitleInput) { mainTitleInput.oninput = updateMainTitleCharCount; } // Button text input character count update for (let i = 1; i <= 4; i++) { const input = document.getElementById(`button-text-${i}`); if (input) { input.oninput = function() { updateButtonCharCount(i); }; } } // Reset to initial state isEditingMainTitle = false; isEditingButtons = false; // Update preview updateButtonPreview(); } function updateMainTitleCharCount() { const mainTitleInput = document.getElementById('generated-main-title'); const charCountEl = document.getElementById('main-title-char-count'); if (mainTitleInput && charCountEl) { const length = mainTitleInput.value.length; charCountEl.textContent = `${length} chars`; if (length === 0) { charCountEl.className = 'char-count-display warning'; } else if (length > 50) { charCountEl.className = 'char-count-display warning'; } else { charCountEl.className = 'char-count-display success'; } } } function updateButtonCharCount(buttonNum) { const input = document.getElementById(`button-text-${buttonNum}`); const charCountEl = input?.parentElement?.querySelector('.char-count-display'); if (input && charCountEl) { const length = input.value.length; charCountEl.textContent = `${length}/30`; if (length === 0 || length > 25) { charCountEl.className = 'char-count-display text-xs warning'; } else { charCountEl.className = 'char-count-display text-xs'; } } } function updateButtonPreview() { const previewButtons = document.getElementById('preview-buttons'); const previewContainer = document.getElementById('internal-link-preview'); if (!previewButtons || selectedRssPosts.length === 0) return; // Show preview container if (previewContainer) { previewContainer.classList.remove('hidden'); } previewButtons.innerHTML = generatedButtonTexts.map((text, idx) => { if (selectedRssPosts[idx]) { return ` ${text} `; } return ''; }).join(''); } // Other functions remain the same function handleSelectAllTopics() { const topicIdeasList = document.getElementById('topic-ideas-list'); if (!topicIdeasList) return; const checkboxes = topicIdeasList.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach(checkbox => { checkbox.checked = true; const listItem = checkbox.closest('li'); if (listItem) { listItem.classList.add('selected'); } }); updateSelectedTopicCount(); validateScheduleSettings(); updateBulkDateInfo(); } function handleDeselectAllTopics() { const topicIdeasList = document.getElementById('topic-ideas-list'); if (!topicIdeasList) return; const checkboxes = topicIdeasList.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach(checkbox => { checkbox.checked = false; const listItem = checkbox.closest('li'); if (listItem) { listItem.classList.remove('selected'); } }); updateSelectedTopicCount(); validateScheduleSettings(); updateBulkDateInfo(); } function validateScheduleSettings() { const topicIdeasList = document.getElementById('topic-ideas-list'); const scheduleTimesInput = document.getElementById('schedule-times-input'); const scheduleDaysInput = document.getElementById('schedule-days-input'); const validationMessageEl = document.getElementById('schedule-validation-message'); const calculationDisplayEl = document.getElementById('schedule-calculation-display'); if (!topicIdeasList || !scheduleTimesInput || !scheduleDaysInput) return; const selectedCheckboxes = topicIdeasList.querySelectorAll('input[type="checkbox"]:checked'); const selectedCount = selectedCheckboxes.length; const timesText = scheduleTimesInput.value.trim(); const daysValue = scheduleDaysInput.value.trim(); if (selectedCount === 0) { if (validationMessageEl) validationMessageEl.innerHTML = ''; if (calculationDisplayEl) calculationDisplayEl.innerHTML = ''; return; } const times = timesText.split(',').map(t => t.trim()).filter(Boolean); const days = parseInt(daysValue, 10); if (times.length === 0 || isNaN(days) || days < 1) { if (validationMessageEl) validationMessageEl.innerHTML = ''; if (calculationDisplayEl) calculationDisplayEl.innerHTML = ''; return; } const postsPerDay = times.length; const totalSlots = postsPerDay * days; const exactDaysNeeded = Math.ceil(selectedCount / postsPerDay); let calculationHtml = `
Calculation: ${postsPerDay} posts/day ${days} days = ${totalSlots} slots | Selected topics: ${selectedCount} ${totalSlots !== selectedCount ? `
Days needed: ${exactDaysNeeded} days` : ''}
`; if (calculationDisplayEl) { calculationDisplayEl.innerHTML = calculationHtml; } let validationHtml = ''; if (totalSlots === selectedCount) { validationHtml = `
Perfect match! ${selectedCount} topics will be scheduled over ${days} days.
`; } else if (totalSlots < selectedCount) { const shortage = selectedCount - totalSlots; validationHtml = `
${shortage} topics won't be scheduled. Change to ${exactDaysNeeded} days.
`; } else { const excess = totalSlots - selectedCount; validationHtml = `
${excess} empty slots will occur. Change to ${exactDaysNeeded} days.
`; } if (validationMessageEl) { validationMessageEl.innerHTML = validationHtml; lucide.createIcons(); } } function updateBulkDateInfo() { const bulkStartDateInput = document.getElementById('bulk-start-date-input'); const bulkDateInfo = document.getElementById('bulk-date-info'); const bulkDateRange = document.getElementById('bulk-date-range'); const scheduleDaysInput = document.getElementById('schedule-days-input'); const scheduleTimesInput = document.getElementById('schedule-times-input'); const topicIdeasList = document.getElementById('topic-ideas-list'); if (!bulkStartDateInput || !bulkDateInfo || !bulkDateRange) return; const startDate = bulkStartDateInput.value; const days = parseInt(scheduleDaysInput?.value || 1, 10); if (!startDate || isNaN(days)) { bulkDateInfo.classList.add('hidden'); return; } const start = new Date(startDate); const end = new Date(startDate); end.setDate(end.getDate() + days - 1); const times = scheduleTimesInput?.value.split(',').map(t => t.trim()).filter(Boolean) || []; const selectedCount = topicIdeasList ? topicIdeasList.querySelectorAll('input[type="checkbox"]:checked').length : 0; const postsPerDay = times.length; const totalSlots = postsPerDay * days; let infoHtml = ` Start: ${start.toLocaleDateString('en-US')}
End: ${end.toLocaleDateString('en-US')}
Period: ${days} days
`; if (postsPerDay > 0) { infoHtml += ` Daily posts: ${postsPerDay} times (${times.join(', ')})
Total slots: ${totalSlots}
`; if (selectedCount > 0) { infoHtml += ` Selected topics: ${selectedCount} `; if (totalSlots === selectedCount) { infoHtml += ` Perfect match`; } else if (totalSlots < selectedCount) { infoHtml += ` ${selectedCount - totalSlots} short`; } else { infoHtml += ` ${totalSlots - selectedCount} excess`; } } } bulkDateRange.innerHTML = infoHtml; bulkDateInfo.classList.remove('hidden'); } function setDefaultDates() { const today = new Date(); const bulkStartDateInput = document.getElementById('bulk-start-date-input'); if (bulkStartDateInput && !bulkStartDateInput.value) { const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, '0'); const day = String(today.getDate()).padStart(2, '0'); bulkStartDateInput.value = `${year}-${month}-${day}`; } } async function fetchScheduledPostsWithTimes() { try { const data = await fetchWithAuth( `https://www.googleapis.com/blogger/v3/blogs/${selectedBlogId}/posts?status=SCHEDULED&maxResults=500&fields=items(title,published)` ); scheduledPostTitles.clear(); scheduledPostTimes.clear(); (data.items || []).forEach(post => { scheduledPostTitles.add(post.title.toLowerCase()); const publishDate = new Date(post.published); const dateKey = publishDate.toDateString(); const timeKey = `${String(publishDate.getHours()).padStart(2, '0')}:${String(publishDate.getMinutes()).padStart(2, '0')}`; if (!scheduledPostTimes.has(dateKey)) { scheduledPostTimes.set(dateKey, new Set()); } scheduledPostTimes.get(dateKey).add(timeKey); }); return { titles: scheduledPostTitles, times: scheduledPostTimes }; } catch (error) { console.error('Failed to fetch scheduled posts:', error); return { titles: new Set(), times: new Map() }; } } async function createSmartSchedule(topics, times, days, shouldGenerateImages, useGeminiSearch, startDateStr) { const bulkQueue = []; const skippedSlots = []; const processedTopics = []; await fetchScheduledPostsWithTimes(); const startDate = new Date(startDateStr); let topicIndex = 0; for (let dayOffset = 0; dayOffset < days && topicIndex < topics.length; dayOffset++) { const currentDate = new Date(startDate); currentDate.setDate(currentDate.getDate() + dayOffset); const dateKey = currentDate.toDateString(); for (const timeStr of times) { if (topicIndex >= topics.length) break; const [hour, minute] = timeStr.split(':').map(Number); const publishDate = new Date(currentDate); publishDate.setHours(hour, minute, 0, 0); const now = new Date(); if (publishDate <= now) { skippedSlots.push({ date: currentDate.toLocaleDateString('en-US'), time: timeStr, reason: 'Past time' }); continue; } if (scheduledPostTimes.has(dateKey) && scheduledPostTimes.get(dateKey).has(timeStr)) { skippedSlots.push({ date: currentDate.toLocaleDateString('en-US'), time: timeStr, reason: 'Conflicts with existing' }); continue; } bulkQueue.push({ topic: topics[topicIndex], publishDate: publishDate, generateImage: shouldGenerateImages, useGeminiSearch: useGeminiSearch }); processedTopics.push(topics[topicIndex]); topicIndex++; } } return { bulkQueue, skippedSlots, processedTopics }; } // RSS URL normalization function normalizeUrl(url) { if (!url) return ''; url = url.trim(); if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://' + url; } return url.replace(/\/$/, ''); } // RSS URL generation function generateRssUrls(blogUrl) { const urls = []; if (blogUrl.includes('blogspot.com')) { urls.push(`${blogUrl}/feeds/posts/default`); urls.push(`${blogUrl}/feeds/posts/default?alt=rss`); } else if (blogUrl.includes('tistory.com')) { urls.push(`${blogUrl}/rss`); } else if (blogUrl.includes('wordpress.com')) { urls.push(`${blogUrl}/feed/`); } else if (blogUrl.includes('blog.naver.com')) { const match = blogUrl.match(/blog\.naver\.com\/(.+)/); if (match) { urls.push(`https://rss.blog.naver.com/${match[1]}.xml`); } } else { urls.push(`${blogUrl}/rss`); urls.push(`${blogUrl}/feed`); urls.push(`${blogUrl}/rss.xml`); } return urls; } // RSS parsing function parseRss(xmlText) { try { const parser = new DOMParser(); const doc = parser.parseFromString(xmlText, 'text/xml'); if (doc.querySelector('parsererror')) { throw new Error('XML parsing error'); } let items = doc.querySelectorAll('item'); let isAtom = false; if (items.length === 0) { items = doc.querySelectorAll('entry'); isAtom = true; } const posts = []; items.forEach((item, index) => { if (index >= 25) return; let title, link, pubDate; if (isAtom) { title = item.querySelector('title')?.textContent?.trim() || `Post ${index + 1}`; const linkElement = item.querySelector('link[rel="alternate"]') || item.querySelector('link'); link = linkElement?.getAttribute('href') || linkElement?.textContent || ''; pubDate = item.querySelector('published')?.textContent || item.querySelector('updated')?.textContent || ''; } else { title = item.querySelector('title')?.textContent?.trim() || `Post ${index + 1}`; link = item.querySelector('link')?.textContent?.trim() || ''; pubDate = item.querySelector('pubDate')?.textContent || ''; } if (link && title) { title = title .replace(/</g, '<') .replace(/>/g, '>') .replace(/&/g, '&') .replace(/"/g, '"'); posts.push({ title, link, pubDate }); } }); return posts; } catch (error) { console.error('RSS parsing error:', error); return []; } } // Fetch RSS via proxy async function fetchRssWithProxy(rssUrl, proxyUrl) { try { let fetchUrl; if (proxyUrl.includes('allorigins.win')) { fetchUrl = proxyUrl + encodeURIComponent(rssUrl); } else { fetchUrl = proxyUrl + rssUrl; } const response = await fetch(fetchUrl); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } let data; if (proxyUrl.includes('allorigins.win')) { const json = await response.json(); data = json.contents; } else { data = await response.text(); } if (data.includes(' 0) { posts = parsedPosts; success = true; showStatusMessage(` Successfully fetched ${posts.length} posts!`, 'success'); break; } } catch (error) { console.error('RSS fetch failed:', error); continue; } } if (success) break; } if (success) { displayRssPosts(posts); document.getElementById('rss-posts-container').classList.remove('hidden'); } else { throw new Error('Could not fetch RSS feed.'); } } catch (error) { console.error('RSS fetch failed:', error); showStatusMessage('Could not fetch RSS feed. Please check the blog address.', 'error'); } finally { fetchButton.disabled = false; fetchButton.innerHTML = ' Fetch RSS'; lucide.createIcons(); } } // Display RSS posts function displayRssPosts(posts) { const container = document.getElementById('rss-posts-list'); container.innerHTML = ''; selectedRssPosts = []; posts.forEach((post, index) => { const dateStr = post.pubDate ? new Date(post.pubDate).toLocaleDateString('en-US') : ''; const postDiv = document.createElement('div'); postDiv.className = 'rss-post-item'; postDiv.innerHTML = `
`; const checkbox = postDiv.querySelector('input[type="checkbox"]'); checkbox.addEventListener('change', (e) => { if (e.target.checked) { if (selectedRssPosts.length >= 4) { e.target.checked = false; showStatusMessage('Maximum 4 posts can be selected.', 'warn'); return; } selectedRssPosts.push({ title: post.title, link: post.link }); postDiv.classList.add('selected'); } else { selectedRssPosts = selectedRssPosts.filter(p => p.link !== post.link); postDiv.classList.remove('selected'); } updateSelectedPostsCount(); if (selectedRssPosts.length === 4) { generateMainTitle(); generateButtonTexts(); document.getElementById('internal-link-config').classList.remove('hidden'); document.getElementById('internal-char-count-info').classList.remove('hidden'); setupTitleAndButtonEditing(); } else { document.getElementById('internal-link-config').classList.add('hidden'); document.getElementById('internal-link-preview').classList.add('hidden'); document.getElementById('internal-char-count-info').classList.add('hidden'); } }); container.appendChild(postDiv); }); } // Update selected posts count function updateSelectedPostsCount() { const countElement = document.getElementById('selected-posts-count'); if (countElement) { countElement.textContent = `${selectedRssPosts.length}/4 selected`; countElement.className = selectedRssPosts.length === 4 ? 'text-sm text-green-600 font-bold' : 'text-sm text-gray-600'; } } // Improved button text generation function - using Pollinations API with language support async function generateButtonTexts() { const previewContainer = document.getElementById('internal-link-preview'); const previewButtons = document.getElementById('preview-buttons'); previewContainer.classList.remove('hidden'); previewButtons.innerHTML = '
AI is generating clickable button text...
'; try { const langInstruction = getLanguageInstructions(selectedLanguage); const buttonPrompt = ` ${langInstruction} Analyze these 4 blog post titles and generate attractive, clickable button text for each in ${languageConfig[selectedLanguage].name}. [Original Post Titles] 1. ${selectedRssPosts[0].title} 2. ${selectedRssPosts[1].title} 3. ${selectedRssPosts[2].title} 4. ${selectedRssPosts[3].title} [Button Text Generation Rules] 1. Each button must be concise within 20 characters 2. No generic phrases like "Read More", "View More" 3. Express the core value or benefit directly 4. Include specific content that sparks curiosity 5. Include 1 emoji per button (related to content) 6. Keep core keywords but make them more attractive 7. Clearly express what information users will get Provide only 4 button texts in ${languageConfig[selectedLanguage].name} (no explanations): Button1: [text] Button2: [text] Button3: [text] Button4: [text] `; const response = await pollinationsFetch('gemini-2.5-flash-lite', buttonPrompt); const lines = response.trim().split('\n'); generatedButtonTexts = lines.map((line, idx) => { const match = line.match(/Button\d+:\s*(.+)/); if (match) { return match[1].trim(); } // Use original title if matching fails const title = selectedRssPosts[idx].title; const emojis = ['', '', '', '']; const shortTitle = title.length > 15 ? title.substring(0, 15) + '...' : title; return `${emojis[idx]} ${shortTitle}`; }); // Update preview updateButtonPreview(); showStatusMessage(' Clickable button text generated!', 'success'); } catch (error) { console.error('Button text generation failed:', error); // Generate based on original titles on failure const emojis = ['', '', '', '']; generatedButtonTexts = selectedRssPosts.map((post, idx) => { const title = post.title; const shortTitle = title.length > 15 ? title.substring(0, 15) : title; return `${emojis[idx]} Check ${shortTitle}`; }); updateButtonPreview(); } } // Improved main title auto-generation - using Pollinations API with language support async function generateMainTitle() { const generatedTitleInput = document.getElementById('generated-main-title'); generatedTitleInput.value = ' AI is analyzing 4 posts to generate an attractive title...'; try { const langInstruction = getLanguageInstructions(selectedLanguage); const titlePrompt = ` ${langInstruction} Analyze these 4 blog post titles and generate an attractive, comprehensive main title in ${languageConfig[selectedLanguage].name}. [4 Post Titles to Analyze] 1. ${selectedRssPosts[0].title} 2. ${selectedRssPosts[1].title} 3. ${selectedRssPosts[2].title} 4. ${selectedRssPosts[3].title} [Title Generation Criteria] 1. Identify common themes and core values from all 4 posts 2. Include specific benefits or information readers will get 3. Include SEO-optimized core keywords 4. Include numbers or specific content to increase click rate 5. Within 30 characters, clear and specific 6. No vague expressions (e.g., complete guide, everything) 7. Clearly express what readers will gain 8. Write in ${languageConfig[selectedLanguage].name} only [Prohibited Words - NEVER USE] - "1st", "#1", "best", "top", "BEST", "TOP", "recommended" - "everything", "perfect", "complete", "all" - No exaggerated expressions or ranking words - Use only objective and factual expressions Provide only 1 creative title in ${languageConfig[selectedLanguage].name} (no quotes, no explanation): `; const response = await pollinationsFetch('gemini-2.5-flash-lite', titlePrompt); let generatedTitle = response.trim().replace(/^["']|["']$/g, ''); // Filter prohibited words const prohibitedWords = ['1st', '#1', 'best', 'top', 'BEST', 'TOP', 'No.1', 'everything', 'perfect', 'complete', 'all', 'recommended']; prohibitedWords.forEach(word => { const regex = new RegExp(word, 'gi'); generatedTitle = generatedTitle.replace(regex, ''); }); // Clean up title generatedTitle = generatedTitle.replace(/\s+/g, ' ').trim(); // Generate default title if too short or empty if (generatedTitle.length < 5) { const keywords = selectedRssPosts.map(p => { const words = p.title.split(' '); return words[0] || ''; }).filter(Boolean); generatedTitle = `${keywords[0]} Essential Info Guide`; } generatedMainTitle = generatedTitle; generatedTitleInput.value = generatedMainTitle; updateMainTitleCharCount(); showStatusMessage(' AI generated an attractive title!', 'success'); } catch (error) { console.error('Title generation failed:', error); // Generate default title on failure const firstTitle = selectedRssPosts[0].title; const keywords = firstTitle.split(' ').slice(0, 3).join(' '); generatedMainTitle = `${keywords} Essential Guide`; generatedTitleInput.value = generatedMainTitle; updateMainTitleCharCount(); } } // RSS internal link posting generation - using Pollinations API and Gemini Search with language support async function handleGenerateInternalLink() { if (selectedRssPosts.length !== 4) { showStatusMessage('Please select exactly 4 posts.', 'error'); return; } const publishTime = document.getElementById('internal-publish-time-input').value; const useGeminiSearch = document.getElementById('use-gemini-search-internal').checked; const shouldGenerateImage = document.getElementById('generate-image-checkbox-internal').checked; if (!generatedMainTitle) { showStatusMessage('Main title not generated.', 'error'); return; } isProcessRunning = true; initLog(); document.getElementById('progress-container').classList.remove('hidden'); globalStartTime = Date.now(); try { addLog(' Starting RSS internal link post generation', 'info'); addLog(` Language: ${languageConfig[selectedLanguage].name}`, 'info'); addLog(` Main title: ${generatedMainTitle}`, 'info'); addLog(` Selected posts: ${selectedRssPosts.length}`, 'info'); addLog(` Generate images: ${shouldGenerateImage ? 'Yes' : 'No'}`, 'info'); // Gemini Search (optional) let searchResults = null; if (useGeminiSearch) { updateProgress(10, ' Searching with Gemini for related resources...'); searchResults = await geminiSearch(generatedMainTitle); if (searchResults) { addLog(' Search complete! Writing with latest information', 'success'); } } updateProgress(30, 'AI is creating high-quality internal link post...'); // Generate internal link button HTML (using improved button text) const internalLinkButtons = selectedRssPosts.map((post, idx) => { const buttonText = generatedButtonTexts[idx] || ` ${post.title.substring(0, 20)}`; return `
${buttonText}
`; }).join('\n'); // Improved internal link prompt - applying guidelines with language support const langInstruction = getLanguageInstructions(selectedLanguage); let internalLinkPrompt = ` ${langInstruction} **Topic:** ${generatedMainTitle} **4 Posts to Link Internally:** ${selectedRssPosts.map((post, idx) => `${idx + 1}. ${post.title}`).join('\n')} **Button Texts:** ${generatedButtonTexts.map((text, idx) => `${idx + 1}. ${text}`).join('\n')} ${searchResults ? ` **Latest Information to Reference:** ${searchResults} ` : ''} **Special Instructions:** 1. Write high-quality content of 10,000+ characters centered on "${generatedMainTitle}" 2. Place 4 internal link buttons at natural positions in the content 3. Button placement positions: - First button: After introduction (20% of total content) - Second button: Middle section (40% of total content) - Third button: Later section (60% of total content) - Fourth button: Before conclusion (80% of total content) 4. Write related explanation paragraphs before and after each button (introduce buttons naturally) 5. Insert button HTML exactly as follows: ${internalLinkButtons} **General Guidelines:** ${WRITING_INSTRUCTIONS || defaultInstructions} Write everything in ${languageConfig[selectedLanguage].name}. `; const articleContent = await pollinationsFetch('gemini-2.5-flash-lite', internalLinkPrompt); // Calculate character count const textContent = articleContent.replace(/<[^>]*>/g, ''); const charCount = textContent.length; addLog(` Writing complete! (Total ${charCount.toLocaleString()} characters)`, 'success'); // Image generation (optional) let finalContent = articleContent; if (shouldGenerateImage) { updateProgress(50, ' Generating AI images...'); // Generate image prompts const imagePrompts = [generatedMainTitle]; const h2Regex = /]*>(.*?)<\/h2>/gi; const h2Matches = [...finalContent.matchAll(h2Regex)]; if (h2Matches.length >= 1) { imagePrompts.push(h2Matches[Math.floor(h2Matches.length / 2)][1].trim()); } const imageUrls = []; for (let i = 0; i < imagePrompts.length; i++) { const p = imagePrompts[i]; addLog(` Generating image ${i+1}/${imagePrompts.length}: "${p.substring(0, 30)}..."`, 'info'); const imageUrl = await generatePollinationsImage(p); imageUrls.push({ url: imageUrl, alt: p }); addLog(` Image ${i+1} generated!`, 'success'); } // Insert images addLog(' Inserting images into content...', 'info'); let imageTags = imageUrls.map(img => { const altText = img.alt.replace(/"/g, '"'); return `
${altText}
${altText}
`; }); if (imageTags.length > 0) { const separator = ''; const paragraphs = finalContent.split(/(<\/p>)/); const assembledParts = []; for (let i = 0; i < paragraphs.length; i += 2) { assembledParts.push(paragraphs[i] + (paragraphs[i + 1] || '')); } if (assembledParts.length > 0) { const thumbnailTag = imageTags.shift(); assembledParts[0] = assembledParts[0] + `\n${thumbnailTag}\n${separator}`; } finalContent = assembledParts.join(''); } addLog(` ${imageUrls.length} images inserted`, 'success'); } updateProgress(70, 'Generating SEO tags...'); const langTagInstruction = getLanguageInstructions(selectedLanguage); const tagsPrompt = `${langTagInstruction}Generate 5-7 SEO optimized tags related to the following topic in ${languageConfig[selectedLanguage].name}: Topic: ${generatedMainTitle} Related posts: ${selectedRssPosts.map(p => p.title).join(', ')} Prohibited words: recommended, 1st, #1, best, TOP, BEST Provide only comma-separated tags in ${languageConfig[selectedLanguage].name}:`; const tagsText = await pollinationsFetch('gemini-2.5-flash-lite', tagsPrompt); const labels = processLabels(tagsText.split(',').map(tag => tag.trim()).filter(Boolean)); updateProgress(90, 'Preparing to publish post...'); const postData = { title: generatedMainTitle, content: finalContent, labels: labels }; await publishPost(postData, publishTime); updateProgress(100, ' RSS internal link post complete!'); // Calculate total elapsed time const totalElapsed = Math.floor((Date.now() - globalStartTime) / 1000); addLog(` RSS internal link post complete! (Total ${totalElapsed} seconds)`, 'success'); // Reset selectedRssPosts = []; selectedMultiBlogPosts = []; totalSelectedPosts = 0; generatedMainTitle = ''; generatedButtonTexts = []; document.getElementById('generated-main-title').value = ''; document.getElementById('internal-publish-time-input').value = ''; document.getElementById('multi-blog-rss-container').classList.add('hidden'); document.getElementById('internal-link-config').classList.add('hidden'); document.getElementById('internal-char-count-info').classList.add('hidden'); document.getElementById('selected-posts-summary').classList.add('hidden'); // Reset multi-blog inputs const blogInputs = document.querySelectorAll('.multi-blog-input'); blogInputs.forEach(input => { input.value = ''; }); } catch (error) { console.error('RSS internal link post generation failed:', error); addLog(` Error: ${error.message}`, 'error'); showStatusMessage('RSS internal link post generation failed.', 'error'); } finally { isProcessRunning = false; } } // Permalink validation and formatting function function formatPermalink(permalink) { if (!permalink) return ''; return permalink .toLowerCase() .replace(/\s+/g, '-') .replace(/[^a-z0-9-]/g, '') .replace(/-+/g, '-') .replace(/^-|-$/g, ''); } // Permalink preview update function updatePermalinkPreview(inputElement, previewElement) { const permalink = inputElement.value.trim(); if (permalink && selectedBlogUrl) { const formatted = formatPermalink(permalink); previewElement.innerHTML = `URL Preview: ${selectedBlogUrl}${formatted}.html`; previewElement.classList.remove('hidden'); } else { previewElement.classList.add('hidden'); } } // Gemini Search function with language support async function geminiSearch(query, jobIndex = null, totalJobs = null) { try { const searchProgressIndicator = document.getElementById('search-progress-indicator'); const searchProgressText = document.getElementById('search-progress-text'); if (searchProgressIndicator) { searchProgressIndicator.classList.remove('hidden'); if (searchProgressText) { if (jobIndex && totalJobs) { searchProgressText.textContent = `Searching with Gemini... (${jobIndex}/${totalJobs}) "${query.substring(0, 30)}..."`; } else { searchProgressText.textContent = `Searching with Gemini for "${query.substring(0, 30)}..."`; } } } addLog(` Starting Gemini search: "${query}"`, 'info'); const langInstruction = getLanguageInstructions(selectedLanguage); const searchPrompt = `${langInstruction}Search for the latest information about "${query}" and provide comprehensive, current information in ${languageConfig[selectedLanguage].name}. Include: 1. Recent developments and updates 2. Key facts and statistics 3. Important details and context 4. Current trends and insights 5. Relevant examples and applications Provide detailed, factual information that would be useful for writing a blog post about this topic. Focus on accuracy and comprehensiveness. Write everything in ${languageConfig[selectedLanguage].name}.`; const searchResults = await pollinationsFetch('gemini-search', searchPrompt); if (searchResults) { addLog(` Gemini search completed successfully`, 'success'); // Display search results preview if in single mode const singleSearchResultPreview = document.getElementById('single-search-result-preview'); if (singleSearchResultPreview && !singleSearchResultPreview.classList.contains('hidden')) { const previewText = searchResults.substring(0, 500); singleSearchResultPreview.innerHTML = ` Gemini Search Results Preview:
${previewText}...
`; } return searchResults; } else { addLog(' No search results found', 'info'); return null; } } catch (error) { console.error('Gemini search failed:', error); addLog(` Search failed: ${error.message}`, 'error'); return null; } finally { const searchProgressIndicator = document.getElementById('search-progress-indicator'); if (searchProgressIndicator) { setTimeout(() => { searchProgressIndicator.classList.add('hidden'); }, 1000); } } } // Pollinations API call function with language support async function pollinationsFetch(model, prompt, systemPrompt = null) { if (!POLLINATIONS_TOKEN) { throw new Error('Pollinations token not configured.'); } // Ensure output in selected language const langInstruction = getLanguageInstructions(selectedLanguage); const languagePrompt = prompt + `\n\n${langInstruction}IMPORTANT: Write all content in ${languageConfig[selectedLanguage].name} only.`; const messages = []; if (systemPrompt) { messages.push({ role: "system", content: systemPrompt }); } messages.push({ role: "user", content: languagePrompt }); const apiUrl = "https://text.pollinations.ai/openai"; const payload = { model: model, messages: messages }; try { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${POLLINATIONS_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`); } const result = await response.json(); if (!result.choices || !result.choices[0]?.message?.content) { throw new Error("No valid text response received from API."); } return result.choices[0].message.content.trim(); } catch (error) { console.error('Pollinations API call failed:', error); throw error; } } // Pollinations image generation function async function generatePollinationsImage(prompt) { try { const enhancedPrompt = `${prompt}, professional photography, high quality, detailed, 8k resolution, beautiful lighting, vibrant colors`; const encodedPrompt = encodeURIComponent(enhancedPrompt); const seed = Date.now() + Math.floor(Math.random() * 10000); const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=1200&height=800&nologo=true&seed=${seed}&token=${POLLINATIONS_TOKEN}`; console.log('Pollinations image URL:', imageUrl); addLog(' Image generated', 'success'); return imageUrl; } catch (error) { console.error('Image generation failed:', error); addLog(' Image generation failed, using default image', 'warn'); const fallbackSeed = Date.now(); return `https://image.pollinations.ai/prompt/beautiful%20blog%20image?width=1200&height=800&nologo=true&seed=${fallbackSeed}&token=${POLLINATIONS_TOKEN}`; } } // Initialization and view management window.onload = () => { if (!window.google || !window.google.accounts) { console.log('Waiting for Google API to load...'); const checkGoogleApi = setInterval(() => { if (window.google && window.google.accounts) { clearInterval(checkGoogleApi); console.log('Google API loaded!'); initializeAfterGoogleLoad(); } }, 100); } else { initializeAfterGoogleLoad(); } }; function initializeAfterGoogleLoad() { loadSettings(); if (CLIENT_ID && POLLINATIONS_TOKEN) { const googleLoginContainer = document.getElementById('google-login-container'); if (googleLoginContainer) { googleLoginContainer.classList.remove('hidden'); } initializeGsi(); } lucide.createIcons(); isAutoRefreshEnabled = localStorage.getItem('autoRefreshToken') === 'true'; const autoRefreshToggle = document.getElementById('auto-refresh-toggle'); if (autoRefreshToggle) { autoRefreshToggle.checked = isAutoRefreshEnabled; updateAutoRefreshStatus(); } startRealtimeClock(); setDefaultDates(); setupEventListeners(); setupLanguageSelection(); // Initialize language selection } function setupEventListeners() { const saveSettingsButton = document.getElementById('save-settings-button'); const googleLoginButton = document.getElementById('google-login-button'); const logoutButtonSelection = document.getElementById('logout-button-selection'); const logoutButtonMain = document.getElementById('logout-button-main'); const changeBlogBtn = document.getElementById('change-blog-btn'); const generateTopicsButton = document.getElementById('generate-topics-button'); const selectAllTopicsButton = document.getElementById('select-all-topics-button'); const deselectAllTopicsButton = document.getElementById('deselect-all-topics-button'); const startBulkScheduleButton = document.getElementById('start-bulk-schedule-button'); const previewScheduleButton = document.getElementById('preview-schedule-button'); const addKeywordButton = document.getElementById('add-keyword-button'); const aiPublishButton = document.getElementById('ai-publish-button'); const saveInstructionsButton = document.getElementById('save-instructions-button'); const manualPublishButton = document.getElementById('manual-publish-button'); const manualPreviewButton = document.getElementById('manual-preview-button'); const manualCodeViewButton = document.getElementById('manual-code-view-button'); const manualResetButton = document.getElementById('manual-reset-button'); const stopButton = document.getElementById('stop-button'); const resumeButton = document.getElementById('resume-button'); const pauseResumeBtn = document.getElementById('pause-resume-btn'); const clearQueueBtn = document.getElementById('clear-queue-btn'); const tabs = document.getElementById('tabs'); const fetchRssButton = document.getElementById('fetch-rss-button'); const generateInternalLinkButton = document.getElementById('generate-internal-link-button'); const fetchMultiRssButton = document.getElementById('fetch-multi-rss-button'); if (saveSettingsButton) saveSettingsButton.addEventListener('click', handleSaveSettings); if (googleLoginButton) { googleLoginButton.addEventListener('click', () => { if (!window.google || !window.google.accounts) { showStatusMessage('Google API is still loading. Please try again in a moment.', 'error'); return; } if (tokenClient) { tokenClient.requestAccessToken(); } else { showStatusMessage('Initializing Google login. Please try again in a moment.', 'info'); initializeGsi(); setTimeout(() => { if (tokenClient) { tokenClient.requestAccessToken(); } }, 1000); } }); } if (logoutButtonSelection) logoutButtonSelection.addEventListener('click', handleLogout); if (logoutButtonMain) logoutButtonMain.addEventListener('click', handleLogout); if (changeBlogBtn) changeBlogBtn.addEventListener('click', showBlogSelectionModal); if (generateTopicsButton) generateTopicsButton.addEventListener('click', handleGenerateTopics); if (selectAllTopicsButton) selectAllTopicsButton.addEventListener('click', handleSelectAllTopics); if (deselectAllTopicsButton) deselectAllTopicsButton.addEventListener('click', handleDeselectAllTopics); if (startBulkScheduleButton) startBulkScheduleButton.addEventListener('click', handleBulkSchedule); if (previewScheduleButton) previewScheduleButton.addEventListener('click', handlePreviewSchedule); if (addKeywordButton) addKeywordButton.addEventListener('click', addKeywordInput); if (aiPublishButton) aiPublishButton.addEventListener('click', handleAIPublishPost); if (saveInstructionsButton) saveInstructionsButton.addEventListener('click', handleSaveInstructions); if (manualPublishButton) manualPublishButton.addEventListener('click', handleManualPublishPost); if (manualPreviewButton) manualPreviewButton.addEventListener('click', showManualPreview); if (manualCodeViewButton) manualCodeViewButton.addEventListener('click', showManualCodeView); if (manualResetButton) manualResetButton.addEventListener('click', handleManualReset); if (stopButton) stopButton.addEventListener('click', handleStopProcess); if (resumeButton) resumeButton.addEventListener('click', handleResumeProcess); if (pauseResumeBtn) pauseResumeBtn.addEventListener('click', handlePauseResume); if (clearQueueBtn) clearQueueBtn.addEventListener('click', handleClearQueue); if (tabs) tabs.addEventListener('click', handleTabClick); if (fetchRssButton) fetchRssButton.addEventListener('click', handleFetchRss); if (generateInternalLinkButton) generateInternalLinkButton.addEventListener('click', handleGenerateInternalLink); if (fetchMultiRssButton) fetchMultiRssButton.addEventListener('click', handleFetchMultiRss); // Gemini Search checkbox events const useGeminiSearchSingle = document.getElementById('use-gemini-search-single'); const useGeminiSearchBulk = document.getElementById('use-gemini-search-bulk'); const useGeminiSearchInternal = document.getElementById('use-gemini-search-internal'); if (useGeminiSearchSingle) { useGeminiSearchSingle.addEventListener('change', (e) => { const singleSearchResultPreview = document.getElementById('single-search-result-preview'); if (e.target.checked) { if (singleSearchResultPreview) singleSearchResultPreview.classList.remove('hidden'); } else { if (singleSearchResultPreview) { singleSearchResultPreview.classList.add('hidden'); singleSearchResultPreview.innerHTML = ''; } } }); } if (useGeminiSearchBulk) { useGeminiSearchBulk.addEventListener('change', (e) => { const bulkSearchInfo = document.getElementById('bulk-search-info'); if (e.target.checked) { if (bulkSearchInfo) bulkSearchInfo.classList.remove('hidden'); } else { if (bulkSearchInfo) bulkSearchInfo.classList.add('hidden'); } }); } // Permalink input event listeners const aiPermalinkInput = document.getElementById('ai-permalink-input'); const aiPermalinkPreview = document.getElementById('ai-permalink-preview'); const manualPermalinkInput = document.getElementById('manual-permalink-input'); const manualPermalinkPreview = document.getElementById('manual-permalink-preview'); if (aiPermalinkInput) { aiPermalinkInput.addEventListener('input', () => { updatePermalinkPreview(aiPermalinkInput, aiPermalinkPreview); }); } if (manualPermalinkInput) { manualPermalinkInput.addEventListener('input', () => { updatePermalinkPreview(manualPermalinkInput, manualPermalinkPreview); }); } const autoRefreshToggle = document.getElementById('auto-refresh-toggle'); if (autoRefreshToggle) { autoRefreshToggle.addEventListener('change', handleAutoRefreshToggle); } const manualRefreshBtn = document.getElementById('manual-refresh-btn'); if (manualRefreshBtn) { manualRefreshBtn.addEventListener('click', () => extendSession(false)); } const bulkStartDateInput = document.getElementById('bulk-start-date-input'); if (bulkStartDateInput) { bulkStartDateInput.addEventListener('change', updateBulkDateInfo); } const scheduleTimesInput = document.getElementById('schedule-times-input'); const scheduleDaysInput = document.getElementById('schedule-days-input'); if (scheduleTimesInput) { scheduleTimesInput.addEventListener('input', () => { validateScheduleSettings(); updateBulkDateInfo(); }); } if (scheduleDaysInput) { scheduleDaysInput.addEventListener('input', () => { validateScheduleSettings(); updateBulkDateInfo(); }); } document.addEventListener('change', (e) => { if (e.target.classList.contains('topic-checkbox')) { validateScheduleSettings(); updateBulkDateInfo(); } }); document.querySelectorAll('[data-toggle-for]').forEach(button => { button.addEventListener('click', () => { const inputId = button.dataset.toggleFor; const input = document.getElementById(inputId); const icon = button.querySelector('i'); if (input.type === 'password') { input.type = 'text'; icon.setAttribute('data-lucide', 'eye-off'); } else { input.type = 'password'; icon.setAttribute('data-lucide', 'eye'); } lucide.createIcons(); }); }); document.addEventListener('visibilitychange', handleVisibilityChange); } // Continue with remaining functions... // Blog selection modal related functions async function showBlogSelectionModal() { if (isProcessRunning) { showStatusMessage('Cannot change blog while task is running.', 'error'); return; } const blogSelectionModal = document.getElementById('blog-selection-modal'); const blogModalLoader = document.getElementById('blog-modal-loader'); const blogModalList = document.getElementById('blog-modal-list'); if (blogSelectionModal) { blogSelectionModal.classList.add('active'); } if (blogModalLoader) { blogModalLoader.classList.remove('hidden'); } if (blogModalList) { blogModalList.innerHTML = ''; } try { if (userBlogs.length === 0) { const data = await fetchWithAuth('https://www.googleapis.com/blogger/v3/users/self/blogs'); userBlogs = data.items || []; } renderBlogModalList(userBlogs); } catch (error) { if (blogModalList) { blogModalList.innerHTML = `
Cannot load blog list: ${error.message}
`; } } finally { if (blogModalLoader) { blogModalLoader.classList.add('hidden'); } } } function renderBlogModalList(blogs) { const blogModalList = document.getElementById('blog-modal-list'); if (!blogModalList) return; if (blogs.length === 0) { blogModalList.innerHTML = '
No blogs found.
'; return; } blogModalList.innerHTML = blogs.map(blog => { const isCurrentBlog = blog.id === selectedBlogId; return `

${blog.name}

${blog.url}

`; }).join(''); } window.selectBlogFromModal = function(blogId, blogName, blogUrl) { if (blogId === selectedBlogId) { closeBlogSelectionModal(); return; } selectedBlogId = blogId; selectedBlogName = blogName; selectedBlogUrl = blogUrl; const blogTitleEl = document.getElementById('blog-title'); if (blogTitleEl) { blogTitleEl.textContent = blogName; } const blogUrlText = document.getElementById('blog-url-text'); if (blogUrlText) { blogUrlText.textContent = blogUrl; } WRITING_INSTRUCTIONS = localStorage.getItem(`bloggerInstructions_${selectedBlogId}`) || defaultInstructions; const writingInstructionsInput = document.getElementById('writing-instructions-input'); if (writingInstructionsInput) { writingInstructionsInput.value = WRITING_INSTRUCTIONS; } scheduledPostTitles.clear(); scheduledPostTimes.clear(); const aiPermalinkInput = document.getElementById('ai-permalink-input'); const aiPermalinkPreview = document.getElementById('ai-permalink-preview'); const manualPermalinkInput = document.getElementById('manual-permalink-input'); const manualPermalinkPreview = document.getElementById('manual-permalink-preview'); if (aiPermalinkInput && aiPermalinkInput.value) { updatePermalinkPreview(aiPermalinkInput, aiPermalinkPreview); } if (manualPermalinkInput && manualPermalinkInput.value) { updatePermalinkPreview(manualPermalinkInput, manualPermalinkPreview); } closeBlogSelectionModal(); showStatusMessage(`Changed to "${blogName}" blog.`, 'success'); const activeTab = document.querySelector('.tab-button.active'); if (activeTab && activeTab.dataset.tab === 'scheduled-list') { fetchScheduledPosts(); } } window.closeBlogSelectionModal = function() { const blogSelectionModal = document.getElementById('blog-selection-modal'); if (blogSelectionModal) { blogSelectionModal.classList.remove('active'); } } document.addEventListener('click', (e) => { if (e.target.classList.contains('modal-backdrop')) { if (e.target.closest('#blog-selection-modal')) { closeBlogSelectionModal(); } else if (e.target.closest('#token-extend-modal')) { closeTokenModal(); } } }); // Schedule preview functions async function handlePreviewSchedule() { const topicIdeasList = document.getElementById('topic-ideas-list'); const scheduleTimesInput = document.getElementById('schedule-times-input'); const scheduleDaysInput = document.getElementById('schedule-days-input'); const bulkStartDateInput = document.getElementById('bulk-start-date-input'); if (!topicIdeasList) return; const selectedCheckboxes = Array.from(topicIdeasList.querySelectorAll('input[type="checkbox"]:checked')); const selectedTopics = selectedCheckboxes.map(cb => cb.value); const timesText = scheduleTimesInput?.value.trim() || ''; const daysValue = scheduleDaysInput?.value.trim() || ''; const startDate = bulkStartDateInput?.value; if (selectedTopics.length === 0) { showStatusMessage('Please select topics to schedule.', 'error'); return; } const times = timesText.split(',').map(t => t.trim()).filter(Boolean); if (times.length === 0 || !times.every(t => /^\d{1,2}:\d{2}$/.test(t))) { showStatusMessage('Please enter valid time format. (e.g., 09:00, 17:00)', 'error'); return; } const days = parseInt(daysValue, 10); if (isNaN(days) || days < 1) { showStatusMessage('Schedule period must be at least 1 day.', 'error'); return; } if (!startDate) { showStatusMessage('Please select start date.', 'error'); return; } await generateSchedulePreviewSmart(selectedTopics, times, days, startDate); const schedulePreviewContainer = document.getElementById('schedule-preview-container'); if (schedulePreviewContainer) { schedulePreviewContainer.classList.remove('hidden'); } } async function generateSchedulePreviewSmart(topics, times, days, startDateStr) { const schedulePreview = document.getElementById('schedule-preview'); if (!schedulePreview) return; const startDate = new Date(startDateStr); let html = ''; await fetchScheduledPostsWithTimes(); let topicIndex = 0; let totalScheduled = 0; let conflictCount = 0; let pastCount = 0; const postsPerDay = times.length; const totalSlots = postsPerDay * days; const exactDaysNeeded = Math.ceil(topics.length / postsPerDay); if (totalSlots !== topics.length) { if (totalSlots < topics.length) { html += `
Only ${totalSlots} of ${topics.length} topics will be scheduled.
Need ${exactDaysNeeded} days to schedule all topics.
`; } else { html += `
${topics.length} topics only need ${exactDaysNeeded} days.
Currently set to ${days} days, ${days - exactDaysNeeded} days excess.
`; } } for (let dayOffset = 0; dayOffset < days && topicIndex < topics.length; dayOffset++) { const currentDate = new Date(startDate); currentDate.setDate(currentDate.getDate() + dayOffset); const dateKey = currentDate.toDateString(); const dateStr = currentDate.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', weekday: 'short' }); for (const timeStr of times) { if (topicIndex >= topics.length) break; const [hour, minute] = timeStr.split(':').map(Number); const publishDate = new Date(currentDate); publishDate.setHours(hour, minute, 0, 0); const isConflict = scheduledPostTimes.has(dateKey) && scheduledPostTimes.get(dateKey).has(timeStr); const now = new Date(); const isPastTime = publishDate <= now; if (isConflict) { html += `
${dateStr} ${timeStr} - [Scheduled] Skip
`; conflictCount++; continue; } if (isPastTime) { html += `
${dateStr} ${timeStr} - [Past] Skip
`; pastCount++; continue; } totalScheduled++; html += `
${dateStr} ${timeStr} - ${topics[topicIndex].substring(0, 30)}${topics[topicIndex].length > 30 ? '...' : ''}
`; topicIndex++; } } const smartSummaryHtml = `
Smart Scheduling Result:
Start date: ${startDate.toLocaleDateString('en-US')}
Selected topics: ${topics.length}
Actual scheduled: ${totalScheduled}
${conflictCount > 0 ? `
Conflicts with existing: ${conflictCount} (auto skip)
` : ''} ${pastCount > 0 ? `
Past times: ${pastCount} (auto skip)
` : ''} ${totalScheduled > 0 ? `
Successfully schedulable: ${totalScheduled}
` : ''}
`; if (totalScheduled === 0) { html = smartSummaryHtml + '
No schedulable slots available.
'; } else { html = smartSummaryHtml + html; } schedulePreview.innerHTML = html; } // Real-time clock function function startRealtimeClock() { if (realtimeClockInterval) { clearInterval(realtimeClockInterval); } const updateClock = () => { const now = new Date(); const timeString = now.toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); const realtimeClockEl = document.getElementById('realtime-clock'); if (realtimeClockEl) { realtimeClockEl.textContent = ` ${timeString}`; } }; updateClock(); realtimeClockInterval = setInterval(updateClock, 1000); } // Next posting time display function updateNextPostingTime() { const nextPostingTimeEl = document.getElementById('next-posting-time'); if (bulkScheduleQueue.length === 0 || !bulkScheduleQueue[0].publishDate) { if (nextPostingTimeEl) { nextPostingTimeEl.textContent = ''; } return; } const nextJob = bulkScheduleQueue[0]; const nextTime = new Date(nextJob.publishDate); const now = new Date(); const diff = nextTime - now; if (diff > 0) { const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((diff % (1000 * 60)) / 1000); const timeString = nextTime.toLocaleString('en-US', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }); let remainingText = ''; if (days > 0) { remainingText = `${days}d ${hours}h ${minutes}m ${seconds}s`; } else if (hours > 0) { remainingText = `${hours}h ${minutes}m ${seconds}s`; } else if (minutes > 0) { remainingText = `${minutes}m ${seconds}s`; } else { remainingText = `${seconds}s`; } if (nextPostingTimeEl) { nextPostingTimeEl.innerHTML = ` Next scheduled: ${timeString} (in ${remainingText})`; } } } // Queue management functions function updateQueueStatus() { const queueStatus = document.getElementById('queue-status'); const queueInfo = document.getElementById('queue-info'); const totalInQueue = bulkScheduleQueue.length; if (totalInQueue > 0 || isProcessPaused) { if (queueStatus) { queueStatus.classList.remove('hidden'); } if (queueInfo) { queueInfo.textContent = `Pending tasks: ${totalInQueue} ${isProcessPaused ? '(Paused)' : ''}`; } if (nextPostingTimeInterval) { clearInterval(nextPostingTimeInterval); } updateNextPostingTime(); nextPostingTimeInterval = setInterval(updateNextPostingTime, 1000); } else { if (queueStatus) { queueStatus.classList.add('hidden'); } if (nextPostingTimeInterval) { clearInterval(nextPostingTimeInterval); } } } function handlePauseResume() { const pauseResumeBtn = document.getElementById('pause-resume-btn'); const currentStatusEl = document.getElementById('current-status'); const progressBar = document.getElementById('progress-bar'); if (isProcessPaused) { isProcessPaused = false; if (pauseResumeBtn) { pauseResumeBtn.innerHTML = ' Pause'; } addLog(' Resuming task', 'info'); if (progressBar) { progressBar.classList.remove('paused'); } if (pausedQueue.length > 0) { bulkScheduleQueue = [...pausedQueue, ...bulkScheduleQueue]; pausedQueue = []; processBulkScheduleQueue(); } } else { isProcessPaused = true; if (pauseResumeBtn) { pauseResumeBtn.innerHTML = ' Resume'; } addLog(' Pausing task', 'warn'); if (progressBar) { progressBar.classList.add('paused'); } if (currentStatusEl) { currentStatusEl.classList.add('paused-indicator'); } } updateQueueStatus(); lucide.createIcons(); } function handleClearQueue() { if (bulkScheduleQueue.length === 0) { showStatusMessage('No pending tasks', 'info'); return; } if (confirm(`Cancel all ${bulkScheduleQueue.length} pending tasks?`)) { const clearedCount = bulkScheduleQueue.length; bulkScheduleQueue = []; pausedQueue = []; updateQueueStatus(); addLog(` ${clearedCount} pending tasks cancelled`, 'warn'); showStatusMessage(`${clearedCount} tasks cancelled`, 'info'); } } // Task stop/resume functions function handleStopProcess() { if (!confirm('Completely stop the running task?\n(To resume, you must start from the beginning)')) { return; } isProcessRunning = false; isProcessPaused = false; bulkScheduleQueue = []; pausedQueue = []; if (waitingInterval) { clearInterval(waitingInterval); waitingInterval = null; } const message = completedBulkJobs > 0 ? `Task stopped. (${completedBulkJobs}/${totalBulkJobs} completed)` : 'Task stopped.'; addLog(message, 'error'); updateProgress((completedBulkJobs / (totalBulkJobs || 1)) * 100, message); setPublishButtonState(false, 'bulk'); const currentStatusEl = document.getElementById('current-status'); const progressBar = document.getElementById('progress-bar'); const stopButton = document.getElementById('stop-button'); const resumeButton = document.getElementById('resume-button'); if (currentStatusEl) { currentStatusEl.textContent = 'Task stopped'; currentStatusEl.classList.remove('paused-indicator'); } if (progressBar) { progressBar.classList.add('error'); progressBar.classList.remove('paused'); } updateQueueStatus(); if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } if (stopButton) stopButton.classList.add('hidden'); if (resumeButton) resumeButton.classList.add('hidden'); } function handleResumeProcess() { if (pausedQueue.length === 0 && bulkScheduleQueue.length === 0) { showStatusMessage('No tasks to resume', 'info'); return; } isProcessRunning = true; isProcessPaused = false; addLog(' Resuming task', 'info'); const progressBar = document.getElementById('progress-bar'); const currentStatusEl = document.getElementById('current-status'); const stopButton = document.getElementById('stop-button'); const resumeButton = document.getElementById('resume-button'); if (progressBar) { progressBar.classList.remove('paused', 'error'); } if (currentStatusEl) { currentStatusEl.classList.remove('paused-indicator'); } if (stopButton) stopButton.classList.remove('hidden'); if (resumeButton) resumeButton.classList.add('hidden'); if (pausedQueue.length > 0) { bulkScheduleQueue = [...pausedQueue, ...bulkScheduleQueue]; pausedQueue = []; } updateQueueStatus(); processBulkScheduleQueue(); } // Token management functions function updateAutoRefreshStatus() { const autoRefreshStatus = document.getElementById('auto-refresh-status'); if (autoRefreshStatus) { autoRefreshStatus.textContent = isAutoRefreshEnabled ? 'On' : 'Off'; autoRefreshStatus.style.color = isAutoRefreshEnabled ? '#22c55e' : '#ef4444'; } updateTokenExplanation(); } function updateTokenExplanation() { const tokenExplanation = document.getElementById('token-explanation'); if (!tokenExplanation) return; if (isAutoRefreshEnabled) { tokenExplanation.innerHTML = ' Auto-refresh ON: Session will auto-extend 10 minutes before expiry.'; } else { tokenExplanation.innerHTML = ' Auto-refresh OFF: Alert will show 5 minutes before expiry. Use "Extend" button for manual extension.'; } } function startTokenCountdown() { const tokenStatusBar = document.getElementById('token-status-bar'); if (tokenCountdownInterval) { clearInterval(tokenCountdownInterval); } if (tokenStatusBar) { tokenStatusBar.classList.remove('hidden'); } tokenCountdownInterval = setInterval(() => { updateTokenDisplay(); }, 1000); updateTokenDisplay(); setupTokenWarning(); if (isAutoRefreshEnabled) { setupAutoRefresh(); } } function updateTokenDisplay() { if (!tokenExpiryTime) return; const now = Date.now(); const remaining = tokenExpiryTime - now; const tokenTimer = document.getElementById('token-timer'); const tokenProgress = document.getElementById('token-progress'); if (remaining <= 0) { clearInterval(tokenCountdownInterval); if (tokenTimer) tokenTimer.textContent = '00:00'; if (tokenProgress) { tokenProgress.style.width = '0%'; tokenProgress.style.backgroundColor = '#ef4444'; } showTokenExpiredAlert(); return; } const totalDuration = TOKEN_DURATION; const percentRemaining = (remaining / totalDuration) * 100; const minutes = Math.floor(remaining / 60000); const seconds = Math.floor((remaining % 60000) / 1000); if (tokenTimer) { tokenTimer.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; } if (tokenProgress) { tokenProgress.style.width = `${percentRemaining}%`; if (remaining <= TOKEN_WARNING_TIME) { tokenProgress.style.backgroundColor = '#ef4444'; tokenTimer?.classList.add('token-warning'); } else if (remaining <= TOKEN_AUTO_REFRESH_TIME) { tokenProgress.style.backgroundColor = '#f59e0b'; tokenTimer?.classList.remove('token-warning'); } else { tokenProgress.style.backgroundColor = '#ffffff'; tokenTimer?.classList.remove('token-warning'); } } if (isAutoRefreshEnabled && !isRefreshing) { const minutesRemaining = Math.floor(remaining / 60000); const secondsRemaining = Math.floor((remaining % 60000) / 1000); if (minutesRemaining === 10 && secondsRemaining <= 1) { console.log(`Auto token refresh (remaining: ${minutesRemaining}m ${secondsRemaining}s)`); extendSession(true); } } } function setupTokenWarning() { if (tokenWarningTimer) { clearTimeout(tokenWarningTimer); } const timeUntilWarning = tokenExpiryTime - Date.now() - TOKEN_WARNING_TIME; if (timeUntilWarning > 0) { tokenWarningTimer = setTimeout(() => { if (!isAutoRefreshEnabled) { showTokenWarningModal(); } }, timeUntilWarning); } } function setupAutoRefresh() { if (tokenRefreshTimer) { clearTimeout(tokenRefreshTimer); tokenRefreshTimer = null; } } function handleAutoRefreshToggle() { const autoRefreshToggle = document.getElementById('auto-refresh-toggle'); if (!autoRefreshToggle) return; isAutoRefreshEnabled = autoRefreshToggle.checked; localStorage.setItem('autoRefreshToken', isAutoRefreshEnabled); updateAutoRefreshStatus(); if (isAutoRefreshEnabled) { showStatusMessage('Auto-refresh enabled. Session will auto-renew 10 minutes before expiry.', 'success'); } else { showStatusMessage('Auto-refresh disabled. Please extend session manually before expiry.', 'info'); } } function extendSession(isAuto = false) { if (!tokenClient) { console.error('Token client not initialized.'); showStatusMessage('Google login initializing. Please try again.', 'error'); return; } if (isRefreshing) { console.log('Already refreshing token.'); return; } isRefreshing = true; try { tokenClient.requestAccessToken({ prompt: '' }); if (!isAuto) { closeTokenModal(); showStatusMessage('Extending session...', 'info'); } else { const notification = document.createElement('div'); notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg z-50'; notification.innerHTML = ' Session auto-extended (10 min before expiry)'; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); addLog(' Session auto-extended (10 min before expiry)', 'success'); } } catch (error) { console.error('Session extension failed:', error); isRefreshing = false; showStatusMessage('Session extension failed. Please login again.', 'error'); } } function showTokenWarningModal() { const tokenExtendModal = document.getElementById('token-extend-modal'); const modalTimeRemaining = document.getElementById('modal-time-remaining'); if (tokenExtendModal) { tokenExtendModal.classList.remove('hidden'); } const modalInterval = setInterval(() => { if (!tokenExpiryTime) { clearInterval(modalInterval); return; } const remaining = tokenExpiryTime - Date.now(); if (remaining <= 0) { clearInterval(modalInterval); closeTokenModal(); return; } const minutes = Math.floor(remaining / 60000); const seconds = Math.floor((remaining % 60000) / 1000); if (modalTimeRemaining) { modalTimeRemaining.textContent = `${minutes}m ${seconds}s`; } }, 1000); } window.closeTokenModal = function() { const tokenExtendModal = document.getElementById('token-extend-modal'); if (tokenExtendModal) { tokenExtendModal.classList.add('hidden'); } } window.extendSession = extendSession; function showTokenExpiredAlert() { showStatusMessage('Session expired. Please login again.', 'error'); setTimeout(() => { handleLogout(); }, 2000); } function handleVisibilityChange() { if (!document.hidden && accessToken && tokenExpiryTime) { const remaining = tokenExpiryTime - Date.now(); if (remaining <= 0) { showTokenExpiredAlert(); } else if (remaining <= TOKEN_WARNING_TIME && !isAutoRefreshEnabled) { showTokenWarningModal(); } updateTokenDisplay(); } } function showView(viewName) { const settingsView = document.getElementById('settings-view'); const blogSelectionView = document.getElementById('blog-selection-view'); const postingView = document.getElementById('posting-view'); if (settingsView) settingsView.style.display = 'none'; if (blogSelectionView) blogSelectionView.style.display = 'none'; if (postingView) postingView.style.display = 'none'; if (viewName === 'settings' && settingsView) settingsView.style.display = 'flex'; if (viewName === 'blog-selection' && blogSelectionView) blogSelectionView.style.display = 'block'; if (viewName === 'posting' && postingView) postingView.style.display = 'block'; } function handleTabClick(e) { const targetTab = e.target.closest('.tab-button'); if (!targetTab) return; document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); targetTab.classList.add('active'); document.querySelectorAll('.tab-content').forEach(content => content.style.display = 'none'); const activeContent = document.getElementById(`${targetTab.dataset.tab}-content`); if (activeContent) activeContent.style.display = 'block'; if (targetTab.dataset.tab === 'scheduled-list') { fetchScheduledPosts(); } } // Settings and authentication function loadSettings() { CLIENT_ID = localStorage.getItem('bloggerClientId'); POLLINATIONS_TOKEN = localStorage.getItem('pollinationsToken'); const clientIdInput = document.getElementById('client-id-input'); const pollinationsTokenInput = document.getElementById('pollinations-token-input'); if (clientIdInput) clientIdInput.value = CLIENT_ID || ''; if (pollinationsTokenInput) pollinationsTokenInput.value = POLLINATIONS_TOKEN || ''; } async function handleSaveSettings() { const clientIdInput = document.getElementById('client-id-input'); const pollinationsTokenInput = document.getElementById('pollinations-token-input'); const settingsErrorEl = document.getElementById('settings-error'); const googleLoginContainer = document.getElementById('google-login-container'); const clientId = clientIdInput?.value.trim(); const pollinationsToken = pollinationsTokenInput?.value.trim(); if (!clientId || !pollinationsToken) { if (settingsErrorEl) { settingsErrorEl.textContent = 'Please enter required fields. (Google OAuth Client ID, Pollinations Token)'; settingsErrorEl.classList.remove('hidden'); } return; } localStorage.setItem('bloggerClientId', clientId); localStorage.setItem('pollinationsToken', pollinationsToken); loadSettings(); if (settingsErrorEl) settingsErrorEl.classList.add('hidden'); if (googleLoginContainer) googleLoginContainer.classList.remove('hidden'); if (window.google && window.google.accounts) { initializeGsi(); } else { showStatusMessage('Loading Google API. Please wait...', 'info'); const checkInterval = setInterval(() => { if (window.google && window.google.accounts) { clearInterval(checkInterval); initializeGsi(); showStatusMessage('Google login ready.', 'success'); } }, 100); } } function initializeGsi() { if (!CLIENT_ID) { console.error('CLIENT_ID not configured.'); return; } if (!window.google || !window.google.accounts) { console.error('Google API not loaded yet.'); showStatusMessage('Google API loading... Please try again.', 'warn'); return; } try { tokenClient = google.accounts.oauth2.initTokenClient({ client_id: CLIENT_ID, scope: 'https://www.googleapis.com/auth/blogger', callback: handleTokenResponse, }); console.log('Google login initialized successfully!'); } catch (error) { console.error('Google login initialization failed:', error); showStatusMessage('Google login initialization failed. Please check Client ID.', 'error'); } } function handleTokenResponse(response) { isRefreshing = false; if (response.error) { if (response.error === 'popup_closed_by_user') { showStatusMessage('Login cancelled.', 'warn'); } else { showStatusMessage('Authentication failed: ' + response.error, 'error'); } if (accessToken) { return; } else { showView('settings'); return; } } const wasLoggedIn = !!accessToken; accessToken = response.access_token; const expiresIn = response.expires_in || 3600; tokenExpiryTime = Date.now() + (expiresIn * 1000); startTokenCountdown(); if (wasLoggedIn) { const minutes = Math.floor((tokenExpiryTime - Date.now()) / 60000); showStatusMessage(` Session extended by ${minutes} minutes.`, 'success'); addLog(` Session extended (${minutes} minutes)`, 'success'); } else { showView('blog-selection'); fetchUserBlogs(); } } function handleLogout() { if (tokenCountdownInterval) { clearInterval(tokenCountdownInterval); } if (tokenRefreshTimer) { clearTimeout(tokenRefreshTimer); } if (tokenWarningTimer) { clearTimeout(tokenWarningTimer); } if (realtimeClockInterval) { clearInterval(realtimeClockInterval); } if (nextPostingTimeInterval) { clearInterval(nextPostingTimeInterval); } if (accessToken) { google.accounts.oauth2.revoke(accessToken, () => { accessToken = null; selectedBlogId = null; selectedBlogName = null; selectedBlogUrl = null; userBlogs = []; tokenExpiryTime = null; isRefreshing = false; const tokenStatusBar = document.getElementById('token-status-bar'); if (tokenStatusBar) { tokenStatusBar.classList.add('hidden'); } showView('settings'); showStatusMessage('Logged out.', 'info'); }); } else { accessToken = null; selectedBlogId = null; selectedBlogName = null; selectedBlogUrl = null; userBlogs = []; tokenExpiryTime = null; isRefreshing = false; const tokenStatusBar = document.getElementById('token-status-bar'); if (tokenStatusBar) { tokenStatusBar.classList.add('hidden'); } showView('settings'); showStatusMessage('Logged out.', 'info'); } } // API call function async function fetchWithAuth(url, options = {}) { if (!accessToken) { throw new Error('Not authenticated.'); } const response = await fetch(url, { ...options, headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', ...options.headers } }); if (!response.ok) { const error = await response.json(); throw new Error(error.error?.message || `HTTP error! status: ${response.status}`); } return response.json(); } // Blog related functions async function fetchUserBlogs() { try { const blogListLoader = document.getElementById('blog-list-loader'); const blogList = document.getElementById('blog-list'); if (blogListLoader) blogListLoader.style.display = 'flex'; if (blogList) blogList.innerHTML = ''; const data = await fetchWithAuth('https://www.googleapis.com/blogger/v3/users/self/blogs'); userBlogs = data.items || []; if (userBlogs.length === 0) { if (blogList) blogList.innerHTML = '
  • No blogs found.
  • '; } else { userBlogs.forEach(blog => { const li = document.createElement('li'); li.className = 'p-3 bg-gray-50 dark:bg-gray-700 rounded cursor-pointer hover:bg-blue-50 dark:hover:bg-blue-900 transition-colors'; li.innerHTML = `
    ${blog.name}
    ${blog.url}
    `; li.addEventListener('click', () => selectBlog(blog)); if (blogList) blogList.appendChild(li); }); } } catch (error) { console.error('Failed to fetch blog list:', error); showStatusMessage('Cannot fetch blog list: ' + error.message, 'error'); } finally { const blogListLoader = document.getElementById('blog-list-loader'); if (blogListLoader) blogListLoader.style.display = 'none'; } } function selectBlog(blog) { selectedBlogId = blog.id; selectedBlogName = blog.name; selectedBlogUrl = blog.url; const blogTitleEl = document.getElementById('blog-title'); if (blogTitleEl) blogTitleEl.textContent = selectedBlogName; const blogUrlText = document.getElementById('blog-url-text'); if (blogUrlText) blogUrlText.textContent = selectedBlogUrl; WRITING_INSTRUCTIONS = localStorage.getItem(`bloggerInstructions_${selectedBlogId}`) || defaultInstructions; const writingInstructionsInput = document.getElementById('writing-instructions-input'); if (writingInstructionsInput) { writingInstructionsInput.value = WRITING_INSTRUCTIONS; } showView('posting'); const firstTab = document.querySelector('.tab-button'); if (firstTab) firstTab.click(); } // Topic generation related functions - reading directly from textarea async function handleGenerateTopics() { const manualTopicsInput = document.getElementById('manual-topics-input'); const userInput = manualTopicsInput?.value.trim(); if (!userInput) { showStatusMessage('Please enter topics.', 'error'); return; } const generateButton = document.getElementById('generate-topics-button'); const topicIdeasLoader = document.getElementById('topic-ideas-loader'); const topicSelectionContainer = document.getElementById('topic-selection-container'); if (generateButton) { generateButton.disabled = true; generateButton.innerHTML = 'Processing...'; } if (topicIdeasLoader) topicIdeasLoader.classList.remove('hidden'); try { // Split textarea input by line breaks to create topic list const topics = userInput.split('\n') .map(line => line.trim()) .filter(topic => topic.length > 0) .slice(0, 30); // Limit to max 30 if (topics.length === 0) { throw new Error('No valid topics found.'); } displayTopicIdeas(topics); if (topicSelectionContainer) topicSelectionContainer.classList.remove('hidden'); const bulkScheduleConfig = document.getElementById('bulk-schedule-config'); if (bulkScheduleConfig) bulkScheduleConfig.classList.remove('hidden'); showStatusMessage(`${topics.length} topics generated!`, 'success'); } catch (error) { console.error('Topic processing failed:', error); showStatusMessage('Topic processing failed. Please try again.', 'error'); } finally { if (topicIdeasLoader) topicIdeasLoader.classList.add('hidden'); if (generateButton) { generateButton.disabled = false; generateButton.innerHTML = 'Generate Topic List'; } lucide.createIcons(); } } function displayTopicIdeas(topics) { const topicIdeasList = document.getElementById('topic-ideas-list'); if (!topicIdeasList) return; topicIdeasList.innerHTML = topics.map((topic, index) => `
  • `).join(''); topicIdeasList.querySelectorAll('.topic-checkbox').forEach(checkbox => { checkbox.addEventListener('change', (e) => { const listItem = e.target.closest('li'); if (e.target.checked) { listItem.classList.add('selected'); } else { listItem.classList.remove('selected'); } updateSelectedTopicCount(); validateScheduleSettings(); updateBulkDateInfo(); }); }); updateSelectedTopicCount(); } function updateSelectedTopicCount() { const topicIdeasList = document.getElementById('topic-ideas-list'); const selectedTopicCount = document.getElementById('selected-topic-count'); const totalTopicCount = document.getElementById('total-topic-count'); if (!topicIdeasList) return; const selectedCheckboxes = topicIdeasList.querySelectorAll('input[type="checkbox"]:checked'); const totalCheckboxes = topicIdeasList.querySelectorAll('input[type="checkbox"]'); if (selectedTopicCount) selectedTopicCount.textContent = selectedCheckboxes.length; if (totalTopicCount) totalTopicCount.textContent = totalCheckboxes.length; } function addKeywordInput() { const container = document.getElementById('keyword-inputs-container'); const existingInputs = container.querySelectorAll('input[name="core-keywords"]'); if (existingInputs.length >= 5) { showStatusMessage('Maximum 5 keywords allowed.', 'warn'); return; } const newInputDiv = document.createElement('div'); newInputDiv.className = 'flex items-center space-x-2'; newInputDiv.innerHTML = ` `; container.appendChild(newInputDiv); lucide.createIcons(); } // Bulk scheduling with language support async function handleBulkSchedule() { const topicIdeasList = document.getElementById('topic-ideas-list'); const scheduleTimesInput = document.getElementById('schedule-times-input'); const scheduleDaysInput = document.getElementById('schedule-days-input'); const scheduleDelayInput = document.getElementById('schedule-delay-input'); const bulkStartDateInput = document.getElementById('bulk-start-date-input'); const shouldGenerateImages = document.getElementById('generate-image-checkbox-bulk').checked; const useGeminiSearch = document.getElementById('use-gemini-search-bulk').checked; if (!topicIdeasList) return; const selectedCheckboxes = Array.from(topicIdeasList.querySelectorAll('input[type="checkbox"]:checked')); const selectedTopics = selectedCheckboxes.map(cb => cb.value); if (selectedTopics.length === 0) { showStatusMessage('Please select topics to schedule.', 'error'); return; } const timesText = scheduleTimesInput?.value.trim() || ''; const times = timesText.split(',').map(t => t.trim()).filter(Boolean); if (times.length === 0 || !times.every(t => /^\d{1,2}:\d{2}$/.test(t))) { showStatusMessage('Please enter valid time format. (e.g., 09:00, 17:00)', 'error'); return; } const days = parseInt(scheduleDaysInput?.value || '1', 10); if (isNaN(days) || days < 1) { showStatusMessage('Schedule period must be at least 1 day.', 'error'); return; } const startDateStr = bulkStartDateInput?.value; if (!startDateStr) { showStatusMessage('Please select start date.', 'error'); return; } const delay = parseInt(scheduleDelayInput?.value || '2', 10); if (isNaN(delay) || delay < 1) { showStatusMessage('Wait time must be at least 1 minute.', 'error'); return; } const postsPerDay = times.length; const totalSlots = postsPerDay * days; if (selectedTopics.length > totalSlots) { const proceed = confirm(`Selected topics (${selectedTopics.length}) exceed available slots (${totalSlots}).\nOnly first ${totalSlots} will be scheduled. Continue?`); if (!proceed) return; } isProcessRunning = true; initLog(); document.getElementById('progress-container').classList.remove('hidden'); addLog(`🌐 Language: ${languageConfig[selectedLanguage].name}`, 'info'); const { bulkQueue, skippedSlots, processedTopics } = await createSmartSchedule( selectedTopics, times, days, shouldGenerateImages, useGeminiSearch, startDateStr ); if (bulkQueue.length === 0) { showStatusMessage('No schedulable slots available.', 'error'); isProcessRunning = false; return; } if (skippedSlots.length > 0) { const conflictSummary = `
    Skipped slots (${skippedSlots.length}): ${skippedSlots.slice(0, 5).map(s => `
    ${s.date} ${s.time} - ${s.reason}
    `).join('')} ${skippedSlots.length > 5 ? `
    ... and ${skippedSlots.length - 5} more
    ` : ''}
    `; document.getElementById('log-list').insertAdjacentHTML('afterbegin', conflictSummary); } bulkScheduleQueue = bulkQueue.map((job, index) => ({ ...job, index: index + 1, delay: delay * 60 * 1000 })); totalBulkJobs = bulkScheduleQueue.length; completedBulkJobs = 0; globalStartTime = Date.now(); addLog(` Starting bulk scheduling: ${totalBulkJobs} posts to schedule`, 'info'); addLog(` Wait time between posts: ${delay} minutes`, 'info'); if (skippedSlots.length > 0) { addLog(` ${skippedSlots.length} slots will be skipped due to conflicts/past times`, 'warn'); } setPublishButtonState(true, 'bulk'); updateQueueStatus(); await processBulkScheduleQueue(); } async function processBulkScheduleQueue() { if (bulkScheduleQueue.length === 0) { isProcessRunning = false; const totalElapsed = Math.floor((Date.now() - globalStartTime) / 1000); const minutes = Math.floor(totalElapsed / 60); const seconds = totalElapsed % 60; addLog(` All tasks complete! (${completedBulkJobs}/${totalBulkJobs} successful, time: ${minutes}m ${seconds}s)`, 'success'); updateProgress(100, ' All scheduling tasks completed!'); setPublishButtonState(false, 'bulk'); updateQueueStatus(); showStatusMessage(`Bulk scheduling complete! ${completedBulkJobs} posts scheduled.`, 'success'); return; } if (isProcessPaused) { pausedQueue = [...bulkScheduleQueue]; bulkScheduleQueue = []; addLog(' Task paused', 'warn'); return; } const currentJob = bulkScheduleQueue.shift(); completedBulkJobs++; const progressPercent = (completedBulkJobs / totalBulkJobs) * 100; updateProgress(progressPercent, `Processing... (${completedBulkJobs}/${totalBulkJobs})`); try { addLog(` [${completedBulkJobs}/${totalBulkJobs}] Starting "${currentJob.topic}"`, 'info'); const postData = await createAIPost( currentJob.topic, WRITING_INSTRUCTIONS || defaultInstructions, currentJob.generateImage, null, currentJob.useGeminiSearch ); const publishDateISO = currentJob.publishDate.toISOString(); await publishPost(postData, publishDateISO); const publishTimeStr = currentJob.publishDate.toLocaleString('en-US'); addLog(` [${completedBulkJobs}/${totalBulkJobs}] "${currentJob.topic}" scheduled (${publishTimeStr})`, 'success'); if (bulkScheduleQueue.length > 0) { const delay = currentJob.delay || 120000; const delayMinutes = Math.floor(delay / 60000); const delaySeconds = Math.floor((delay % 60000) / 1000); addLog(` Waiting ${delayMinutes}m ${delaySeconds}s until next task...`, 'info'); updateQueueStatus(); await waitWithCountdown(delay); await processBulkScheduleQueue(); } else { await processBulkScheduleQueue(); } } catch (error) { console.error('Bulk scheduling task failed:', error); addLog(` [${completedBulkJobs}/${totalBulkJobs}] "${currentJob.topic}" failed: ${error.message}`, 'error'); if (bulkScheduleQueue.length > 0 && !isProcessPaused) { const delay = 10000; addLog(` Proceeding to next task in 10 seconds...`, 'info'); await waitWithCountdown(delay); await processBulkScheduleQueue(); } } } async function waitWithCountdown(delay) { return new Promise(resolve => { const startTime = Date.now(); const endTime = startTime + delay; const updateWaitingStatus = () => { if (isProcessPaused) { if (waitingInterval) { clearInterval(waitingInterval); waitingInterval = null; } resolve(); return; } const now = Date.now(); const remaining = Math.max(0, endTime - now); if (remaining === 0) { if (waitingInterval) { clearInterval(waitingInterval); waitingInterval = null; } resolve(); } else { const seconds = Math.ceil(remaining / 1000); const currentStatusEl = document.getElementById('current-status'); if (currentStatusEl) { currentStatusEl.textContent = `Waiting ${seconds}s until next task...`; } } }; updateWaitingStatus(); waitingInterval = setInterval(updateWaitingStatus, 1000); }); } // Single AI posting with language support async function handleAIPublishPost() { const topicInput = document.getElementById('post-topic-input'); const instructionsInput = document.getElementById('writing-instructions-input'); const publishTimeInput = document.getElementById('ai-publish-time-input'); const permalinkInput = document.getElementById('ai-permalink-input'); const shouldGenerateImage = document.getElementById('generate-image-checkbox-single').checked; const useGeminiSearch = document.getElementById('use-gemini-search-single').checked; const topic = topicInput?.value.trim(); const instructions = instructionsInput?.value.trim() || defaultInstructions; const publishTime = publishTimeInput?.value; const customPermalink = permalinkInput?.value.trim(); if (!topic) { showStatusMessage('Please enter post topic.', 'error'); return; } if (!selectedBlogId) { showStatusMessage('Please select a blog.', 'error'); return; } isProcessRunning = true; setPublishButtonState(true, 'single'); initLog(); document.getElementById('progress-container').classList.remove('hidden'); globalStartTime = Date.now(); try { addLog(' Starting AI posting', 'info'); addLog(`🌐 Language: ${languageConfig[selectedLanguage].name}`, 'info'); addLog(` Topic: ${topic}`, 'info'); addLog(` Generate images: ${shouldGenerateImage ? 'Yes' : 'No'}`, 'info'); addLog(` Gemini search: ${useGeminiSearch ? 'Enabled' : 'Disabled'}`, 'info'); const postData = await createAIPost(topic, instructions, shouldGenerateImage, customPermalink, useGeminiSearch); updateProgress(90, 'Publishing post...'); await publishPost(postData, publishTime); const totalElapsed = Math.floor((Date.now() - globalStartTime) / 1000); updateProgress(100, ' Posting complete!'); addLog(` Posting complete! (Total ${totalElapsed} seconds)`, 'success'); if (topicInput) topicInput.value = ''; if (publishTimeInput) publishTimeInput.value = ''; if (permalinkInput) permalinkInput.value = ''; showStatusMessage('AI posting successfully published!', 'success'); } catch (error) { console.error('AI posting failed:', error); addLog(` Error: ${error.message}`, 'error'); showStatusMessage('AI posting failed: ' + error.message, 'error'); } finally { isProcessRunning = false; setPublishButtonState(false, 'single'); } } // AI post creation function with language support async function createAIPost(topic, instructions, shouldGenerateImage, customPermalink = null, useGeminiSearch = false) { const steps = shouldGenerateImage ? 4 : 3; let currentStep = 0; // Gemini Search (optional) let searchResults = null; if (useGeminiSearch) { currentStep++; updateProgress((currentStep/steps) * 100, `Step ${currentStep}/${steps}: Searching with Gemini...`); addLog(` Searching for latest information about "${topic}"...`, 'info'); searchResults = await geminiSearch(topic); if (searchResults) { addLog(` Search complete! Writing with latest information`, 'success'); } } // Write article currentStep++; updateProgress((currentStep/steps) * 100, `Step ${currentStep}/${steps}: AI writing in ${languageConfig[selectedLanguage].name}`); addLog(` Dino AI has started writing in ${languageConfig[selectedLanguage].name}...`, 'info'); const langInstruction = getLanguageInstructions(selectedLanguage); let articlePrompt = `${langInstruction}**Topic:** ${topic} **Instructions:** ${instructions} IMPORTANT: Write everything in ${languageConfig[selectedLanguage].name} only. All content must be in ${languageConfig[selectedLanguage].name}.`; if (searchResults) { articlePrompt = `${langInstruction}**Topic:** ${topic} **Latest Information to Reference:** ${searchResults} **Instructions:** ${instructions} Write a more accurate and reliable article using the latest information above. IMPORTANT: Write everything in ${languageConfig[selectedLanguage].name} only. All content must be in ${languageConfig[selectedLanguage].name}.`; } try { const articleContent = await pollinationsFetch('gemini-2.5-flash-lite', articlePrompt); const textContent = articleContent.replace(/<[^>]*>/g, ''); const charCount = textContent.length; addLog(` Writing complete! (Total ${charCount.toLocaleString()} characters)`, 'success'); let finalContent = cleanHtml(articleContent); // Image generation (optional) if (shouldGenerateImage) { currentStep++; updateProgress((currentStep/steps) * 100, `Step ${currentStep}/${steps}: Generating images`); const h2Regex = /]*>(.*?)<\/h2>/gi; const h2Matches = [...finalContent.matchAll(h2Regex)]; let imagePrompts = [topic]; const imageUrls = []; for (let i = 0; i < imagePrompts.length; i++) { const p = imagePrompts[i]; if (!isProcessRunning) throw new Error('Task stopped'); addLog(` Dino AI image generation started: "${p.substring(0, 30)}..."`, 'info'); const imageUrl = await generatePollinationsImage(p); imageUrls.push({ url: imageUrl, alt: p }); addLog(` Image ${i+1} generated!`, 'success'); } // Insert images addLog(' Inserting images into content...', 'info'); let imageTags = imageUrls.map(img => { const altText = img.alt.replace(/"/g, '"'); return `
    ${altText}
    ${altText}
    `; }); if (imageTags.length > 0) { const separator = ''; const paragraphs = finalContent.split(/(<\/p>)/); const assembledParts = []; for (let i = 0; i < paragraphs.length; i += 2) { assembledParts.push(paragraphs[i] + (paragraphs[i + 1] || '')); } if (assembledParts.length > 0) { const thumbnailTag = imageTags.shift(); assembledParts[0] = assembledParts[0] + `\n${thumbnailTag}\n${separator}`; } finalContent = assembledParts.join(''); } addLog(` ${imageUrls.length} images inserted`, 'success'); } // SEO tag generation currentStep++; updateProgress((currentStep/steps) * 100, `Step ${currentStep}/${steps}: Generating SEO tags`); addLog(` AI generating SEO tags in ${languageConfig[selectedLanguage].name}...`, 'info'); const langTagInstruction = getLanguageInstructions(selectedLanguage); const tagsPrompt = `${langTagInstruction}Analyze the following topic and generate 5 to 7 highly relevant SEO keyword tags (labels) in ${languageConfig[selectedLanguage].name} that are best for search exposure. Each tag should be concise within 15 characters Include only keywords directly related to '${topic}' Never use prohibited words like recommended, 1st, #1, best, TOP, BEST Provide results as comma-separated keyword list only in ${languageConfig[selectedLanguage].name} Topic: ${topic}`; const tagsText = await pollinationsFetch('gemini-2.5-flash-lite', tagsPrompt); let labels = tagsText.split(',').map(tag => tag.trim()).filter(Boolean); // Filter prohibited words const prohibitedWords = ['recommended', '1st', '#1', 'best', 'BEST', 'TOP']; labels = labels.filter(label => { for (const word of prohibitedWords) { if (label.toLowerCase().includes(word.toLowerCase())) { return false; } } return true; }); const processedLabels = processLabels(labels); const labelString = processedLabels.join(', '); addLog(` ${processedLabels.length} SEO tags generated (${labelString.length} chars)`, 'success'); updateProgress(100, ` All content generation complete!`); const postData = { title: topic, content: finalContent, labels: processedLabels }; // Add permalink if (customPermalink) { const formattedPermalink = formatPermalink(customPermalink); if (formattedPermalink) { postData.url = `${selectedBlogUrl}${formattedPermalink}.html`; addLog(` Permalink set: ${formattedPermalink}`, 'info'); } } return postData; } catch (error) { throw error; } } // HTML cleanup function function cleanHtml(rawHtml) { const bodyMatch = rawHtml.match(/]*>([\s\S]*)<\/body>/i); let cleanedContent; if (bodyMatch && bodyMatch[1]) { cleanedContent = bodyMatch[1].trim(); } else { cleanedContent = rawHtml .replace(//i, '') .replace(/]*>/i, '') .replace(/<\/html>/i, '') .replace(/]*>[\s\S]*<\/head>/i, '') .replace(/```html/g, '') .replace(/```/g, '') .trim(); } cleanedContent = cleanedContent.replace(/]*>.*?<\/h1>/gi, ''); cleanedContent = cleanedContent.replace(/<(div|p|section|aside)[^>]*>\s*(tags|related tags|TAGS)\s*:[\s\S]*?<\/\1>/gi, ''); cleanedContent = cleanedContent.replace(/^\s*(tags|related tags|TAGS)\s*:.*$/gim, ''); cleanedContent = cleanedContent.replace(/^.+?<\/[hH][2-3]>/, '').trim(); return cleanedContent.trim(); } // Label processing function function processLabels(labels) { if (!labels || labels.length === 0) return []; let processedLabels = labels.map(label => { const trimmed = label.trim(); return trimmed.length > 20 ? trimmed.substring(0, 20) : trimmed; }); let totalLength = processedLabels.join(',').length; while (totalLength > 150 && processedLabels.length > 1) { processedLabels.pop(); totalLength = processedLabels.join(',').length; } if (totalLength > 150 && processedLabels.length === 1) { const maxLength = 150; processedLabels[0] = processedLabels[0].substring(0, maxLength); } return processedLabels; } // Post publishing async function publishPost(postData, publishTime) { if (!selectedBlogId) { throw new Error('No blog selected.'); } const payload = { kind: 'blogger#post', title: postData.title, content: postData.content, labels: postData.labels }; if (postData.url) { payload.url = postData.url; } if (publishTime) { const publishDate = new Date(publishTime); const now = new Date(); if (publishDate > now) { payload.published = publishDate.toISOString(); } } const url = `https://www.googleapis.com/blogger/v3/blogs/${selectedBlogId}/posts`; const data = await fetchWithAuth(url, { method: 'POST', body: JSON.stringify(payload) }); addLog(` Post published successfully: ${data.title}`, 'success'); return data; } // Manual posting related functions async function handleManualPublishPost() { const titleInput = document.getElementById('manual-title-input'); const contentInput = document.getElementById('manual-content-input'); const labelsInput = document.getElementById('manual-labels-input'); const publishTimeInput = document.getElementById('manual-publish-time-input'); const permalinkInput = document.getElementById('manual-permalink-input'); const title = titleInput?.value.trim(); const content = contentInput?.value.trim(); const labelsText = labelsInput?.value.trim(); const publishTime = publishTimeInput?.value; const customPermalink = permalinkInput?.value.trim(); if (!title || !content) { showStatusMessage('Please enter title and content.', 'error'); return; } if (!selectedBlogId) { showStatusMessage('Please select a blog.', 'error'); return; } try { const labels = labelsText ? labelsText.split(',').map(label => label.trim()).filter(Boolean) : []; const processedLabels = processLabels(labels); const postData = { title: title, content: content, labels: processedLabels }; if (customPermalink) { const formattedPermalink = formatPermalink(customPermalink); if (formattedPermalink) { postData.url = `${selectedBlogUrl}${formattedPermalink}.html`; } } await publishPost(postData, publishTime); if (titleInput) titleInput.value = ''; if (contentInput) contentInput.value = ''; if (labelsInput) labelsInput.value = ''; if (publishTimeInput) publishTimeInput.value = ''; if (permalinkInput) permalinkInput.value = ''; const manualPermalinkPreview = document.getElementById('manual-permalink-preview'); if (manualPermalinkPreview) { manualPermalinkPreview.classList.add('hidden'); } showStatusMessage('Manual post successfully published!', 'success'); } catch (error) { console.error('Manual posting failed:', error); showStatusMessage('Posting failed: ' + error.message, 'error'); } } function showManualPreview() { const contentInput = document.getElementById('manual-content-input'); const contentPreview = document.getElementById('manual-content-preview'); const previewButton = document.getElementById('manual-preview-button'); const codeViewButton = document.getElementById('manual-code-view-button'); if (contentInput && contentPreview) { contentInput.style.display = 'none'; contentPreview.style.display = 'block'; contentPreview.innerHTML = contentInput.value; if (previewButton) previewButton.style.display = 'none'; if (codeViewButton) codeViewButton.style.display = 'inline'; } } function showManualCodeView() { const contentInput = document.getElementById('manual-content-input'); const contentPreview = document.getElementById('manual-content-preview'); const previewButton = document.getElementById('manual-preview-button'); const codeViewButton = document.getElementById('manual-code-view-button'); if (contentInput && contentPreview) { contentInput.style.display = 'block'; contentPreview.style.display = 'none'; if (previewButton) previewButton.style.display = 'inline'; if (codeViewButton) codeViewButton.style.display = 'none'; } } function handleManualReset() { const titleInput = document.getElementById('manual-title-input'); const contentInput = document.getElementById('manual-content-input'); const labelsInput = document.getElementById('manual-labels-input'); const publishTimeInput = document.getElementById('manual-publish-time-input'); const permalinkInput = document.getElementById('manual-permalink-input'); if (titleInput) titleInput.value = ''; if (contentInput) contentInput.value = ''; if (labelsInput) labelsInput.value = ''; if (publishTimeInput) publishTimeInput.value = ''; if (permalinkInput) permalinkInput.value = ''; const manualPermalinkPreview = document.getElementById('manual-permalink-preview'); if (manualPermalinkPreview) { manualPermalinkPreview.classList.add('hidden'); } showManualCodeView(); showStatusMessage('Input fields reset.', 'info'); } function handleSaveInstructions() { const instructionsInput = document.getElementById('writing-instructions-input'); const instructions = instructionsInput?.value.trim(); if (!instructions) { showStatusMessage('Please enter instructions.', 'error'); return; } WRITING_INSTRUCTIONS = instructions; if (selectedBlogId) { localStorage.setItem(`bloggerInstructions_${selectedBlogId}`, instructions); showStatusMessage('Saved as default instructions for this blog.', 'success'); } else { localStorage.setItem('bloggerDefaultInstructions', instructions); showStatusMessage('Saved as default instructions.', 'success'); } } // Scheduled posts list async function fetchScheduledPosts() { const scheduledLoader = document.getElementById('scheduled-loader'); const scheduledListUl = document.getElementById('scheduled-list-ul'); if (!selectedBlogId) { if (scheduledListUl) scheduledListUl.innerHTML = '
  • Please select a blog first.
  • '; if (scheduledLoader) scheduledLoader.style.display = 'none'; return; } if (scheduledLoader) scheduledLoader.style.display = 'flex'; if (scheduledListUl) scheduledListUl.innerHTML = ''; try { const data = await fetchWithAuth( `https://www.googleapis.com/blogger/v3/blogs/${selectedBlogId}/posts?status=SCHEDULED&maxResults=100` ); const posts = data.items || []; if (posts.length === 0) { if (scheduledListUl) scheduledListUl.innerHTML = '
  • No scheduled posts.
  • '; } else { posts.forEach(post => { const li = document.createElement('li'); li.className = 'p-3 bg-gray-50 dark:bg-gray-700 rounded'; const publishDate = new Date(post.published); const dateStr = publishDate.toLocaleString('en-US'); li.innerHTML = `
    ${post.title}
    Scheduled: ${dateStr}
    ID: ${post.id}
    `; if (scheduledListUl) scheduledListUl.appendChild(li); }); } } catch (error) { console.error('Failed to fetch scheduled posts:', error); if (scheduledListUl) scheduledListUl.innerHTML = '
  • Cannot fetch scheduled posts.
  • '; } finally { if (scheduledLoader) scheduledLoader.style.display = 'none'; } } // UI helper functions function setPublishButtonState(isLoading, mode = 'single') { const singleButton = document.getElementById('ai-publish-button'); const bulkButton = document.getElementById('start-bulk-schedule-button'); const stopButton = document.getElementById('stop-button'); const resumeButton = document.getElementById('resume-button'); if (mode === 'single' && singleButton) { singleButton.disabled = isLoading; const buttonText = document.getElementById('ai-publish-button-text'); if (buttonText) { buttonText.textContent = isLoading ? 'Processing...' : 'Execute AI Posting'; } } else if (mode === 'bulk' && bulkButton) { bulkButton.disabled = isLoading; bulkButton.innerHTML = isLoading ? 'Processing...' : 'Start Bulk Scheduling'; } if (stopButton) { stopButton.style.display = isLoading ? 'block' : 'none'; } if (resumeButton) { resumeButton.style.display = 'none'; } lucide.createIcons(); } function updateProgress(percentage, message) { const progressBar = document.getElementById('progress-bar'); const currentStatus = document.getElementById('current-status'); const progressContainer = document.getElementById('progress-container'); if (progressContainer) progressContainer.classList.remove('hidden'); if (progressBar) progressBar.style.width = `${percentage}%`; if (currentStatus && message) currentStatus.textContent = message; if (percentage >= 100) { if (progressBar) { progressBar.classList.add('success'); } } } function initLog() { const logList = document.getElementById('log-list'); const progressContainer = document.getElementById('progress-container'); if (logList) logList.innerHTML = ''; if (progressContainer) progressContainer.classList.remove('hidden'); const progressBar = document.getElementById('progress-bar'); if (progressBar) { progressBar.style.width = '0%'; progressBar.classList.remove('success', 'error', 'paused'); } } function addLog(message, type = 'info') { const logList = document.getElementById('log-list'); if (!logList) return; const li = document.createElement('li'); const now = new Date(); const time = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); let typeClass = ''; if (type === 'success') typeClass = 'log-success'; else if (type === 'error') typeClass = 'log-error'; else if (type === 'warn') typeClass = 'log-warn'; else if (type === 'info') typeClass = 'log-info'; li.innerHTML = `[${time}] ${message}`; logList.appendChild(li); const logContainer = document.getElementById('log-container'); if (logContainer) { logContainer.scrollTop = logContainer.scrollHeight; } } function showStatusMessage(message, type = 'info') { const statusMessage = document.getElementById('status-message'); if (!statusMessage) { console.log(message); return; } statusMessage.textContent = message; statusMessage.className = 'mt-4 p-4 rounded-md text-center'; if (type === 'success') { statusMessage.classList.add('bg-green-100', 'text-green-700'); } else if (type === 'error') { statusMessage.classList.add('bg-red-100', 'text-red-700'); } else if (type === 'warn') { statusMessage.classList.add('bg-yellow-100', 'text-yellow-700'); } else { statusMessage.classList.add('bg-blue-100', 'text-blue-700'); } statusMessage.classList.remove('hidden'); setTimeout(() => { statusMessage.classList.add('hidden'); }, 5000); }