diff --git a/regen_articles.js b/regen_articles.js new file mode 100644 index 0000000..8cdf689 --- /dev/null +++ b/regen_articles.js @@ -0,0 +1,80 @@ +process.chdir('/var/www/zeropost-engine'); +require('dotenv').config(); + +const { query } = require('./src/config/db'); +const ai = require('./src/services/ai'); + +const DELAY_MS = 3000; +const TIMEOUT_MS = 120000; // 2 мин на статью +const START_FROM_ID = parseInt(process.argv[2] || '0'); // можно передать start id + +const sleep = ms => new Promise(r => setTimeout(r, ms)); + +function withTimeout(promise, ms) { + return Promise.race([ + promise, + new Promise((_, reject) => setTimeout(() => reject(new Error('TIMEOUT')), ms)) + ]); +} + +function parseContent(content, topic) { + const lines = content.split('\n').filter(Boolean); + let title = topic; + const h1 = lines.find(l => l.startsWith('# ')); + if (h1) title = h1.replace(/^#\s+/, '').trim(); + const firstPara = lines.find(l => l.length > 80 && !l.startsWith('#')); + const excerpt = firstPara ? firstPara.substring(0, 200) + (firstPara.length > 200 ? '...' : '') : ''; + const wordCount = content.replace(/<[^>]+>/g, '').split(/\s+/).length; + const readingTime = Math.max(1, Math.round(wordCount / 200)); + return { title, excerpt, readingTime }; +} + +async function regenArticle(article, blogChannel) { + const topic = article.source_topic || article.title; + try { + const result = await withTimeout( + ai.generateArticle(blogChannel, { topic }), + TIMEOUT_MS + ); + if (!result?.content) return false; + + const { title, excerpt, readingTime } = parseContent(result.content, topic); + await query( + `UPDATE articles SET title=$1, content=$2, excerpt=$3, reading_time=$4, + seo_title=$5, seo_descr=$6, updated_at=NOW() WHERE id=$7`, + [title, result.content, excerpt, readingTime, + title.substring(0,60), excerpt.substring(0,160), article.id] + ); + console.log(`[Regen] OK id=${article.id} "${title.slice(0,60)}"`); + return true; + } catch (err) { + console.error(`[Regen] ERROR id=${article.id}: ${err.message}`); + return false; + } +} + +async function main() { + const { rows: articles } = await query( + `SELECT id, title, source_topic, category FROM articles + WHERE status='published' ${START_FROM_ID ? `AND id >= ${START_FROM_ID}` : ''} + ORDER BY id ASC` + ); + const { rows: channels } = await query( + `SELECT * FROM channels WHERE is_system=true AND is_active=true LIMIT 1` + ); + + console.log(`[Regen] ${articles.length} articles to process (from id=${START_FROM_ID || 'start'})`); + + let ok = 0, fail = 0; + for (let i = 0; i < articles.length; i++) { + console.log(`[Regen] ${i+1}/${articles.length} id=${articles[i].id}`); + const success = await regenArticle(articles[i], channels[0]); + if (success) ok++; else fail++; + if (i < articles.length - 1) await sleep(DELAY_MS); + } + + console.log(`[Regen] DONE: ${ok} OK, ${fail} FAILED`); + process.exit(0); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/src/services/scheduledPostsRunner.js b/src/services/scheduledPostsRunner.js index dba05be..76e0199 100644 --- a/src/services/scheduledPostsRunner.js +++ b/src/services/scheduledPostsRunner.js @@ -75,9 +75,7 @@ async function publishToTelegram({ channel, text, photoUrl, article }) { const base = await settings.get('TELEGRAM_API_BASE', 'https://api.telegram.org'); // Inline-кнопка — только если есть статья и кнопка не отключена - const buttonText = channel.auto_publish_button_text === null - ? null - : (channel.auto_publish_button_text || DEFAULT_BUTTON_TEXT); + const buttonText = channel.auto_publish_button_text || DEFAULT_BUTTON_TEXT; let reply_markup = undefined; if (article && buttonText) { reply_markup = {