feat: Зеро-персонаж, auto-publish, auto-series, channel-stats, fallback covers

- Персонаж Зеро: 23 позы (zeroCharacter.js), скрипты генерации
- Auto-publish статей в TG: multipart upload, кнопки, режим alternating Zero/cover
- Fallback цепочка обложек: aiprimetech gpt-5.5 → Pollinations → local SVG (6 палитр)
- Auto-series: Claude haiku определяет серию для каждой статьи автоматически
- Channel stats: подписчики, история, delta 24h/7d
- Photo-search: Yandex API, профили доменов, Redis лимиты
- Scheduled posts runner: backfill, preview, queue, cancel
- promptBuilder: author_persona Зеро, голос от первого лица
- Fixes: dollar-placeholder bugs в PATCH channels/autogen, listArticles фильтры
- AI model: gpt-5.5 для image generation
This commit is contained in:
Nik (Claude)
2026-06-07 14:03:56 +03:00
parent 8968eed3e0
commit a370b8f7d8
33 changed files with 2695 additions and 147 deletions
+8 -7
View File
@@ -1,14 +1,15 @@
const { query } = require('../config/db');
const axios = require('axios');
const settings = require('../services/settings');
/**
* Сохранить пост в базу (как черновик или сразу запланированный).
*/
async function savePost({ userId, channelId, content, imageUrl = null, topic = null, status = 'draft', scheduledAt = null }) {
async function savePost({ userId, channelId, content, imageUrl = null, imageCredit = null, topic = null, status = 'draft', scheduledAt = null }) {
const { rows } = await query(
`INSERT INTO user_posts (user_id, channel_id, content, image_url, topic, status, scheduled_at)
VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING *`,
[userId, channelId, content, imageUrl, topic, status, scheduledAt]
`INSERT INTO user_posts (user_id, channel_id, content, image_url, image_credit, topic, status, scheduled_at)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING *`,
[userId, channelId, content, imageUrl, imageCredit, topic, status, scheduledAt]
);
return rows[0];
}
@@ -30,7 +31,7 @@ async function getPost(userId, postId) {
}
async function updatePost(userId, postId, data) {
const allowed = ['content','image_url','status','scheduled_at','topic'];
const allowed = ['content','image_url','image_credit','status','scheduled_at','topic'];
const fields = []; const vals = []; let i = 1;
for (const key of allowed) {
if (data[key] !== undefined) { fields.push(`${key}=$${i++}`); vals.push(data[key]); }
@@ -63,7 +64,7 @@ async function publishToTelegram(post, channel) {
? post.image_url
: `https://app.zeropost.ru${post.image_url}`;
const res = await axios.post(
`https://api.telegram.org/bot${channel.bot_token}/sendPhoto`,
`${await settings.get('TELEGRAM_API_BASE', 'https://api.telegram.org')}/bot${channel.bot_token}/sendPhoto`,
{
chat_id: channel.tg_channel_id,
photo: photoUrl,
@@ -75,7 +76,7 @@ async function publishToTelegram(post, channel) {
return { ok: true, message_id: res.data?.result?.message_id };
} else {
const res = await axios.post(
`https://api.telegram.org/bot${channel.bot_token}/sendMessage`,
`${await settings.get('TELEGRAM_API_BASE', 'https://api.telegram.org')}/bot${channel.bot_token}/sendMessage`,
{
chat_id: channel.tg_channel_id,
text: post.content,