feat: bot assembly, scheduler, and entry point
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { Telegraf } from 'telegraf';
|
||||
import type { Database } from 'better-sqlite3';
|
||||
import { handleLog } from './commands/log';
|
||||
import { handleSkip } from './commands/skip';
|
||||
import { handleSnooze } from './commands/snooze';
|
||||
import { handleStatus } from './commands/status';
|
||||
import { handleHistory } from './commands/history';
|
||||
import { handleStreak } from './commands/streak';
|
||||
import { handleSettings } from './commands/settings-cmd';
|
||||
import { handleProgress } from './commands/progress';
|
||||
import { handleNext } from './commands/next';
|
||||
import { handleReschedule } from './commands/reschedule';
|
||||
import { handleChat } from './commands/chat';
|
||||
import { handleSetup, handleSetupReply } from './commands/setup';
|
||||
|
||||
export function createBot(token: string, chatId: string, db: Database): Telegraf {
|
||||
const bot = new Telegraf(token);
|
||||
|
||||
bot.command('log', handleLog(db));
|
||||
bot.command('skip', handleSkip(db));
|
||||
bot.command('snooze', handleSnooze(db, chatId));
|
||||
bot.command('status', handleStatus(db));
|
||||
bot.command('history', handleHistory(db));
|
||||
bot.command('streak', handleStreak(db));
|
||||
bot.command('settings', handleSettings(db));
|
||||
bot.command('progress', handleProgress(db));
|
||||
bot.command('next', handleNext(db));
|
||||
bot.command('reschedule', handleReschedule(db));
|
||||
bot.command('chat', handleChat(db));
|
||||
bot.command('setup', handleSetup(db));
|
||||
|
||||
const setupReply = handleSetupReply(db);
|
||||
bot.on('text', async (ctx) => {
|
||||
const text = (ctx.message as { text?: string })?.text ?? '';
|
||||
if (text.startsWith('/')) return;
|
||||
await setupReply(ctx);
|
||||
});
|
||||
|
||||
bot.catch((err) => {
|
||||
console.error('Bot error:', err);
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { config } from './config';
|
||||
import { getDb } from './db';
|
||||
import { createBot } from './bot';
|
||||
import { startScheduler } from './scheduler';
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const db = getDb(config.dbPath);
|
||||
const bot = createBot(config.telegramToken, config.telegramChatId, db);
|
||||
|
||||
startScheduler(bot, db, config.telegramChatId);
|
||||
|
||||
await bot.launch();
|
||||
console.log('Training assistant bot started.');
|
||||
|
||||
process.once('SIGINT', () => bot.stop('SIGINT'));
|
||||
process.once('SIGTERM', () => bot.stop('SIGTERM'));
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Fatal error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import cron from 'node-cron';
|
||||
import type { Telegraf } from 'telegraf';
|
||||
import type { Database } from 'better-sqlite3';
|
||||
import { getSetting } from '../db/settings';
|
||||
import { config } from '../config';
|
||||
import { sendMorningReminder, sendEveningNudge } from './reminders';
|
||||
|
||||
function parseCronTime(hhmm: string): string {
|
||||
const [hh, mm] = hhmm.split(':');
|
||||
return `${mm} ${hh} * * *`;
|
||||
}
|
||||
|
||||
export function startScheduler(bot: Telegraf, db: Database, chatId: string): void {
|
||||
const morningTime = getSetting(db, 'morning_reminder') ?? config.morningReminder;
|
||||
const eveningTime = getSetting(db, 'evening_reminder') ?? config.eveningReminder;
|
||||
|
||||
cron.schedule(parseCronTime(morningTime), async () => {
|
||||
try {
|
||||
await sendMorningReminder(bot, db, chatId);
|
||||
} catch (err) {
|
||||
console.error('Morning reminder failed:', err);
|
||||
}
|
||||
});
|
||||
|
||||
cron.schedule(parseCronTime(eveningTime), async () => {
|
||||
try {
|
||||
await sendEveningNudge(bot, db, chatId);
|
||||
} catch (err) {
|
||||
console.error('Evening nudge failed:', err);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Reminders scheduled: morning=${morningTime}, evening=${eveningTime}`);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Telegraf } from 'telegraf';
|
||||
import type { Database } from 'better-sqlite3';
|
||||
import { todayString } from '../utils/date';
|
||||
import { getScheduleByDate } from '../db/schedule';
|
||||
import { getSessionByDate } from '../db/sessions';
|
||||
import { formatDuration } from '../utils/duration';
|
||||
|
||||
export async function sendMorningReminder(bot: Telegraf, db: Database, chatId: string): Promise<void> {
|
||||
const today = todayString();
|
||||
const scheduled = getScheduleByDate(db, today);
|
||||
if (!scheduled || scheduled.status !== 'pending') return;
|
||||
|
||||
const session = getSessionByDate(db, today);
|
||||
if (session) return;
|
||||
|
||||
await bot.telegram.sendMessage(
|
||||
chatId,
|
||||
`Good morning! You have a ${formatDuration(scheduled.planned_min)} session planned today.\n\nUse /log when done, /skip if you can't make it, or /snooze to remind you later.`
|
||||
);
|
||||
}
|
||||
|
||||
export async function sendEveningNudge(bot: Telegraf, db: Database, chatId: string): Promise<void> {
|
||||
const today = todayString();
|
||||
const scheduled = getScheduleByDate(db, today);
|
||||
if (!scheduled || scheduled.status !== 'pending') return;
|
||||
|
||||
const session = getSessionByDate(db, today);
|
||||
if (session) return;
|
||||
|
||||
await bot.telegram.sendMessage(
|
||||
chatId,
|
||||
`Still time for today's session (${formatDuration(scheduled.planned_min)})!\n\nUse /log to record it, or /skip to mark it as skipped.`
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user