← Back
const Parser = require('rss-parser');
const axios = require('axios');
const { aiConfig } = require('../config');

const rssParser = new Parser({
  timeout: 10000,
  headers: { 'User-Agent': 'Mozilla/5.0 (compatible; KZBot/1.0)' },
  customFields: { item: ['quant', 'change', 'description'] }
});

function log(msg) {
  const ts = new Date().toLocaleString('en-CA', { timeZone: 'America/Vancouver' });
  console.log(`[${ts}] ${msg}`);
}

const NB_RATES_URL = 'https://nationalbank.kz/rss/rates_all.xml';

// Валюты для поста (код → флаг)
const WATCH = [
  { code: 'USD', flag: '🇺🇸', name: 'Доллар' },
  { code: 'EUR', flag: '🇪🇺', name: 'Евро' },
  { code: 'RUB', flag: '🇷🇺', name: 'Рубль' },
  { code: 'CNY', flag: '🇨🇳', name: 'Юань' }
];

// Дата по Казахстану в формате "13 июня"
function kzDateLabel() {
  const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
    'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
  const s = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Almaty' }); // YYYY-MM-DD
  const [, m, d] = s.split('-');
  return `${parseInt(d, 10)} ${months[parseInt(m, 10) - 1]}`;
}

// Курс валют от Нацбанка РК → готовый пост (или null при сбое)
async function getCurrencyPost(hashtags = []) {
  try {
    const feed = await rssParser.parseURL(NB_RATES_URL);
    const byCode = {};
    for (const item of feed.items) {
      if (item.title) byCode[item.title.trim().toUpperCase()] = item;
    }

    const lines = [];
    for (const w of WATCH) {
      const it = byCode[w.code];
      if (!it) continue;
      const val = parseFloat(it.description);
      if (!isFinite(val)) continue;
      const ch = parseFloat(it.change);
      let arrow = '';
      if (isFinite(ch) && ch > 0) arrow = ` 🔺${ch.toFixed(2)}`;
      else if (isFinite(ch) && ch < 0) arrow = ` 🔻${Math.abs(ch).toFixed(2)}`;
      lines.push(`${w.flag} ${w.code} — <b>${val.toFixed(2)} ₸</b>${arrow}`);
    }

    if (lines.length === 0) return null;

    let text = `💱 <b>Курс валют на ${kzDateLabel()}</b>\n\n${lines.join('\n')}\n\n`;
    text += `Официальный курс Нацбанка РК. Как вам тенге сегодня? 👇`;
    if (hashtags.length > 0) text += `\n\n${hashtags.join(' ')}`;
    return text;
  } catch (err) {
    log(`❌ Курс валют: ${err.message}`);
    return null;
  }
}

// --- Рубрика «Опрос дня» ---

// Статичные опросы-фолбэки по типу канала (если AI не сгенерил)
const FALLBACK_POLLS = {
  news: { question: 'Какое настроение сегодня? 🇰🇿', options: ['🔥 Отличное', '😐 Нормально', '😴 Так себе', '😤 Лучше не спрашивай'] },
  earnings: { question: 'Сколько хочешь зарабатывать в месяц? 💰', options: ['до 300к ₸', '300–700к ₸', '700к–1.5млн ₸', 'больше 1.5млн ₸'] },
  sports: { question: 'За какой спорт болеешь больше? 🇰🇿', options: ['🥊 Бокс / ММА', '⚽ Футбол', '🏒 Хоккей', '🏋️ Другое'] }
};

// Обрезать строку до лимита Telegram
function clip(s, max) {
  s = (s || '').trim();
  return s.length > max ? s.slice(0, max - 1).trim() + '…' : s;
}

// Сгенерировать опрос через AI (или вернуть фолбэк по типу канала)
async function getPoll(channel) {
  const apiKey = process.env.OPENROUTER_API_KEY;
  const fallback = FALLBACK_POLLS[channel.type] || FALLBACK_POLLS.news;
  if (!apiKey) return fallback;

  const system = `Ты придумываешь короткие вовлекающие опросы для Telegram-канала "${channel.name}" (тематика: ${channel.type}, аудитория Казахстана).
Опрос должен быть лёгким, без политики и негатива, вызывать желание проголосовать.
Ответь СТРОГО в формате (без вступлений и пояснений):
ВОПРОС: <текст вопроса с эмодзи, до 180 символов>
ВАРИАНТ: <вариант 1>
ВАРИАНТ: <вариант 2>
ВАРИАНТ: <вариант 3>
2-4 варианта, каждый до 80 символов.`;

  try {
    const { data } = await axios.post(`${aiConfig.baseUrl}/chat/completions`, {
      model: aiConfig.model,
      messages: [
        { role: 'system', content: system },
        { role: 'user', content: 'Придумай один опрос на сегодня.' }
      ],
      max_tokens: 300,
      temperature: 0.9
    }, {
      headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
      timeout: 15000
    });

    const raw = data.choices?.[0]?.message?.content || '';
    let question = null;
    const options = [];
    for (const line of raw.split('\n')) {
      const q = line.match(/^\s*ВОПРОС:\s*(.+)$/i);
      const o = line.match(/^\s*ВАРИАНТ:\s*(.+)$/i);
      if (q) question = clip(q[1], 290);
      else if (o) options.push(clip(o[1].replace(/^[-*\d.)\s]+/, ''), 95));
    }

    // Валидация: вопрос + 2-4 непустых уникальных варианта
    const uniq = [...new Set(options.filter(Boolean))];
    if (question && uniq.length >= 2) {
      return { question, options: uniq.slice(0, 4) };
    }
    log(`⚠️ Опрос ${channel.name}: AI вернул невалидный формат, фолбэк`);
    return fallback;
  } catch (err) {
    log(`❌ Опрос ${channel.name}: ${err.message}, фолбэк`);
    return fallback;
  }
}

module.exports = { getCurrencyPost, getPoll };

📜 Git History

31dcfdffeat: вечерняя рубрика Опрос дня (sendPoll, AI + статичный фолбэк)3 weeks ago
ecd6693feat: утренняя рубрика Курс валют от Нацбанка РК (B3)3 weeks ago
Show last diff
Loading...