← Back
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...