For security, you will be automatically logged out in .
Please extend your session to continue.
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.
Blog Selection
Select a blog to post to.
Session expires in59:59
Off
Pending tasks: 0
🇺🇸English
🇰🇷한국어
🇯🇵日本語
🇩🇪Deutsch
🇪🇸Español
🇨🇳中文
🇹🇭ไทย
🇮🇳हिन्दी
Selected Language:English
All AI-generated content (posts, titles, tags) will be created in the selected language.
Enter up to 4 blog addresses, fetch RSS feeds, then select a total of 4 posts regardless of blog count. AI will create optimal internal link posts.
Recent Posts by Blog (Select 4 total)
0/4 selected
Recent Posts (Select 4)
0/4 selected
0 chars
AI-generated title from analyzing 4 posts. Click Edit to modify.
Button 1:0/30
Button 2:0/30
Button 3:0/30
Button 4:0/30
Gemini will search for latest information about the generated main title for richer content.
Internal Link Button Preview
Expected character count: Approx. 10,000 chars
1. Enter blog topics directly below (one per line) 2. Topics will be automatically added to the list 3. Set schedule and start the process
Separate each topic with a line break (Enter). Maximum 30 topics allowed.
Topic Selection (0/0)
Schedule Settings
Schedule Period Info:
Enter times to publish each day. (e.g., 09:00, 17:00 = 2 times per day)
Wait time between each post creation to reduce server load.
Search feature enabled:
Searches for latest info for each post
Process: Search Write Post
Completely free (no API key needed)
Leave empty to auto-generate from title
Only lowercase letters, numbers, and hyphens allowed
Spaces will be converted to hyphens
Enter labels/tags for blog SEO (max 20 chars each, total max 150 chars)
Leave empty to auto-generate from title
Only lowercase letters, numbers, and hyphens allowed
Scheduled Posts
Searching with Gemini for resources...
Preparing...
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 = `
${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 `
`;
}).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}`;
});
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 = '