Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
142 lines
5.5 KiB
JavaScript
142 lines
5.5 KiB
JavaScript
// backend/routes/v3/telegram/weeklyFortuna.js
|
|
// Weekly summary and Fortune Wheel commands: /week, /fortuna, /fortune, /wheel
|
|
|
|
import { apiLogger, sendMessage, sendToTopic, dbAll, isPostgres, safeJsonParse, spinFortuneWheel } from './shared.js';
|
|
|
|
/**
|
|
* Handle /week command — Weekly summary (tasks by status for current week).
|
|
*/
|
|
export async function handleWeek(chatId) {
|
|
try {
|
|
// All tasks that were active or completed this week
|
|
const allTasks = await dbAll(
|
|
isPostgres()
|
|
? `SELECT id, data FROM table_rows
|
|
WHERE table_id = 2649
|
|
AND (
|
|
(data->>'state') NOT IN ('Archive')
|
|
OR (
|
|
(data->>'completed_date') IS NOT NULL
|
|
AND (data->>'completed_date')::date >= date_trunc('week', CURRENT_DATE)
|
|
)
|
|
)`
|
|
: `SELECT id, data FROM table_rows
|
|
WHERE table_id = 2649
|
|
AND (
|
|
json_extract(data, '$.state') != 'Archive'
|
|
OR (
|
|
json_extract(data, '$.completed_date') IS NOT NULL
|
|
AND date(json_extract(data, '$.completed_date')) >= date('now', 'weekday 1', '-7 days')
|
|
)
|
|
)`
|
|
);
|
|
|
|
// Tasks completed this week
|
|
const doneThisWeek = await dbAll(
|
|
isPostgres()
|
|
? `SELECT id, data FROM table_rows
|
|
WHERE table_id = 2649
|
|
AND (data->>'state') = 'Done'
|
|
AND (data->>'completed_date') IS NOT NULL
|
|
AND (data->>'completed_date')::date >= date_trunc('week', CURRENT_DATE)`
|
|
: `SELECT id, data FROM table_rows
|
|
WHERE table_id = 2649
|
|
AND json_extract(data, '$.state') = 'Done'
|
|
AND json_extract(data, '$.completed_date') IS NOT NULL
|
|
AND date(json_extract(data, '$.completed_date')) >= date('now', 'weekday 1', '-7 days')`
|
|
);
|
|
|
|
// Count by status
|
|
const statusCounts = {};
|
|
let totalActive = 0;
|
|
for (const row of (allTasks || [])) {
|
|
const d = safeJsonParse(row.data, {});
|
|
const state = d.state || 'Unknown';
|
|
statusCounts[state] = (statusCounts[state] || 0) + 1;
|
|
if (state !== 'Done' && state !== 'Archive') totalActive++;
|
|
}
|
|
|
|
const doneCount = (doneThisWeek || []).length;
|
|
const totalTracked = totalActive + doneCount;
|
|
const completionRate = totalTracked > 0 ? Math.round((doneCount / totalTracked) * 100) : 0;
|
|
|
|
// Week range
|
|
const now = new Date();
|
|
const dayOfWeek = now.getDay();
|
|
const weekStart = new Date(now);
|
|
weekStart.setDate(now.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1));
|
|
const weekEnd = new Date(weekStart);
|
|
weekEnd.setDate(weekStart.getDate() + 6);
|
|
const fmtDate = (d) => d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' });
|
|
|
|
const stateEmoji = { 'Done': '✅', 'In Progress': '🔄', 'To Do': '📌', 'Backlog': '📥', 'Review': '👀', 'Blocked': '🚫' };
|
|
|
|
let msg = `📊 *Итоги недели*\n📅 ${fmtDate(weekStart)} — ${fmtDate(weekEnd)}\n\n`;
|
|
|
|
// Completion rate bar
|
|
const barLen = 10;
|
|
const filled = Math.round((completionRate / 100) * barLen);
|
|
const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
|
|
msg += `*Прогресс:* ${bar} ${completionRate}%\n`;
|
|
msg += `✅ Завершено: ${doneCount} | 📋 Активных: ${totalActive}\n\n`;
|
|
|
|
// Status breakdown
|
|
msg += '*По статусам:*\n';
|
|
for (const [state, count] of Object.entries(statusCounts).sort((a, b) => b[1] - a[1])) {
|
|
const emoji = stateEmoji[state] || '▪️';
|
|
msg += ` ${emoji} ${state}: ${count}\n`;
|
|
}
|
|
msg += '\n';
|
|
|
|
// List completed this week
|
|
if (doneThisWeek && doneThisWeek.length > 0) {
|
|
msg += '*Завершено на этой неделе:*\n';
|
|
for (const row of doneThisWeek.slice(0, 10)) {
|
|
const d = safeJsonParse(row.data, {});
|
|
const title = (d.title || d.name || 'Untitled').substring(0, 50);
|
|
msg += ` ✔️ ${title}\n`;
|
|
}
|
|
if (doneThisWeek.length > 10) {
|
|
msg += ` _...и ещё ${doneThisWeek.length - 10}_\n`;
|
|
}
|
|
}
|
|
|
|
await sendMessage(chatId, msg);
|
|
} catch (err) {
|
|
apiLogger.error({ err }, '[Telegram] /week command failed');
|
|
await sendMessage(chatId, '❌ Ошибка загрузки недельной сводки.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle /fortuna, /fortune, /wheel command — Fortune Wheel: random break activity.
|
|
*/
|
|
export async function handleFortuna(chatId) {
|
|
try {
|
|
const { activity, message: fortuneMsg } = spinFortuneWheel();
|
|
|
|
// Inline keyboard: Done (+15 pts) / Skip
|
|
const inlineKeyboard = {
|
|
reply_markup: JSON.stringify({
|
|
inline_keyboard: [[
|
|
{ text: '✅ Сделал! +15 очков', callback_data: 'fortuna_done' },
|
|
{ text: '❌ Пропустить', callback_data: 'fortuna_skip' }
|
|
]]
|
|
})
|
|
};
|
|
|
|
// 1. Send to the Fortune topic in the group (no buttons in topic)
|
|
const topicResult = await sendToTopic('fortune', fortuneMsg);
|
|
if (!topicResult.success) {
|
|
apiLogger.error({ error: topicResult.error }, '[Telegram] Failed to send fortune to topic');
|
|
}
|
|
|
|
// 2. Send to user's private chat WITH inline buttons
|
|
await sendMessage(chatId, fortuneMsg, inlineKeyboard);
|
|
|
|
apiLogger.info({ activity: activity.name, chatId }, '[Telegram] Fortune wheel spun via /fortuna command');
|
|
} catch (err) {
|
|
apiLogger.error({ err }, '[Telegram] /fortuna command failed');
|
|
await sendMessage(chatId, '❌ Ошибка колеса фортуны. Попробуй ещё раз!');
|
|
}
|
|
}
|