require('dotenv').config({ override: true });
const { Bot } = require('grammy');
const cron = require('node-cron');
const { channels, cronSchedule } = require('./config');
const db = require('./database');
const { processSlot, setBotInstance } = require('./scheduler');
const { startDashboard } = require('./dashboard/server');
const bot = new Bot(process.env.BOT_TOKEN);
// Логирование с timestamp (Vancouver)
function log(msg) {
const ts = new Date().toLocaleString('en-CA', { timeZone: 'America/Vancouver' });
console.log(`[${ts}] ${msg}`);
}
// Проверка что все каналы настроены
function validateChannels() {
const missing = [];
for (const [key, ch] of Object.entries(channels)) {
if (!ch.chatId) missing.push(key);
}
if (missing.length > 0) {
log(`⚠️ Каналы без chatId: ${missing.join(', ')}. Заполни .env`);
}
return missing;
}
// Admin guard
const ADMIN_ID = Number(process.env.ADMIN_ID) || 191142060;
function isAdmin(ctx) {
return ctx.from && ctx.from.id === ADMIN_ID;
}
// Команды бота (только для админа)
bot.command('status', async (ctx) => {
if (!isAdmin(ctx)) return;
const lines = ['📊 *KZ Channels Status*\n'];
for (const [key, ch] of Object.entries(channels)) {
const todayPosts = db.getTodayPostCount(key);
const todayAds = db.getTodayAdCount(key);
const status = ch.chatId ? '✅' : '❌ no chatId';
lines.push(`${status} *${ch.name}*: ${todayPosts} постов, ${todayAds} рекл.`);
}
await ctx.reply(lines.join('\n'), { parse_mode: 'Markdown' });
});
bot.command('stats', async (ctx) => {
if (!isAdmin(ctx)) return;
const lines = ['📈 *Статистика за 7 дней*\n'];
for (const [key, ch] of Object.entries(channels)) {
const stats = db.getStats(key, 7);
const totalPosts = stats.reduce((s, r) => s + r.posts_count, 0);
const totalAds = stats.reduce((s, r) => s + r.ad_posts_count, 0);
lines.push(`*${ch.name}*: ${totalPosts} постов, ${totalAds} рекл.`);
}
await ctx.reply(lines.join('\n'), { parse_mode: 'Markdown' });
});
bot.command('test', async (ctx) => {
if (!isAdmin(ctx)) return;
const args = ctx.message.text.split(' ');
const channelKey = args[1];
if (!channelKey || !channels[channelKey]) {
const keys = Object.keys(channels).join(', ');
return ctx.reply(`Укажи канал: /test <${keys}>`);
}
await ctx.reply(`⏳ Тестовый пост в ${channels[channelKey].name}...`);
try {
await processSlot(bot, channelKey, 'content', true);
await ctx.reply('✅ Отправлен!');
} catch (err) {
await ctx.reply(`❌ Ошибка: ${err.message}`);
}
});
// Настройка cron-задач
function setupCron() {
for (const [slotName, cronExpr] of Object.entries(cronSchedule)) {
cron.schedule(cronExpr, async () => {
log(`⏰ Слот: ${slotName}`);
for (const channelKey of Object.keys(channels)) {
if (channels[channelKey].enabled === false) continue;
if (!channels[channelKey].chatId) continue;
try {
const sent = await processSlot(bot, channelKey, slotName);
if (sent) {
log(`✅ ${channels[channelKey].name} — ${slotName} отправлен`);
}
} catch (err) {
log(`❌ ${channels[channelKey].name} — ${slotName}: ${err.message}`);
require('./error-log').addError(channelKey, 'cron', `${slotName}: ${err.message}`);
}
// Пауза между каналами чтобы не спамить API
await new Promise(r => setTimeout(r, 3000));
}
}, { timezone: 'Asia/Almaty' });
log(`📅 Cron: ${slotName} → ${cronExpr}`);
}
}
// Старт
async function start() {
log('🚀 KZ Channels Bot запускается...');
db.init();
log('💾 Database OK');
setBotInstance(bot);
const missing = validateChannels();
setupCron();
log(`📅 ${Object.keys(cronSchedule).length} cron-задач настроено`);
startDashboard(bot);
log('📊 Dashboard запущен на порту 3220');
bot.start({
onStart: () => log('🤖 Bot polling started'),
allowed_updates: ['message']
});
log(`🇰🇿 KZ Channels Bot запущен! Каналов: ${Object.keys(channels).length}, без chatId: ${missing.length}`);
}
// Graceful shutdown
process.on('SIGINT', () => { db.close(); process.exit(0); });
process.on('SIGTERM', () => { db.close(); process.exit(0); });
start().catch(err => {
console.error('Fatal:', err);
process.exit(1);
});
📜 Git History
afebbfafeat: остановить канал typical-kz (enabled:false + cron guard)3 weeks ago
3fdc0b1fix: 4 бага — WAL checkpoint, fallback контент, HTML sanitize, логи7 weeks ago
0413c12fix: 9 багов — caption limit, orphan cleanup, security hardening7 weeks ago
cec0551fix: cron timezone — сервер UTC, крон был для PDT → 7ч сдвиг7 weeks ago
045688afeat: dashboard v0.3 — 9 новых источников + 10 улучшений дашборда7 weeks ago
5481deffeat: post moderation queue for new sources7 weeks ago
0d25952feat: add dashboard on arb.szhub.space (port 3220)7 weeks ago
2757087init: KZ Channels bot — 5 Telegram channels for Kazakhstan7 weeks ago
Show last diff
Loading...