Добавлена локализация, настройка частоты цитат и админ-панель

This commit is contained in:
Zwuck
2025-12-16 12:08:51 +05:00
parent 21b9c48cea
commit db535c2560
9 changed files with 189 additions and 640 deletions

View File

@@ -1,9 +1,3 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client { generator client {
provider = "prisma-client" provider = "prisma-client"
output = "../generated/prisma" output = "../generated/prisma"
@@ -15,11 +9,13 @@ datasource db {
} }
model User { model User {
id Int @id @default(autoincrement()) // Âíóòðåííèé ID áàçû id Int @id @default(autoincrement()) // Внутренний ID базы
telegramId BigInt @unique // ID èç Òåëåãðàìà (BigInt îáÿçàòåëåí) telegramId BigInt @unique // ID из Телеграма (BigInt обязателен)
username String? // Íèêíåéì (ìîæåò íå áûòü) username String? // Никнейм (может не быть)
fio String? // ÔÈÎ (Èìÿ + Ôàìèëèÿ) fio String? // ФИО (Имя + Фамилия)
createdAt DateTime @default(now()) // Äàòà ïåðâîãî îáðàùåíèÿ ê áîòó createdAt DateTime @default(now()) // Дата первого обращения к боту
frequency Int @default(1) // Частота отправки цитат в часах (1, 3, 5, 7, 9, 12)
lastQuoteSentAt DateTime @default(now()) // Время последней отправки цитаты
@@map("users") // Íåîáÿçàòåëüíî: äåëàåò èìÿ òàáëèöû â ÁÄ "users" (ìíîæåñòâåííîå ÷èñëî) @@map("users") // Необязательно: делает имя таблицы в БД "users" (множественное число)
} }

View File

@@ -0,0 +1,27 @@
import { Controller, Post, Body, UnauthorizedException } from '@nestjs/common';
import { BotService } from '../bot/bot.service';
@Controller('admin')
export class AdminController {
constructor(private readonly botService: BotService) { }
@Post('send-message')
async sendMessage(@Body() body: any) {
// body: { userId: number, password: string, message: string }
const { userId, password, message } = body;
const adminPassword = process.env.ADMIN_PASSWORD;
if (!adminPassword || password !== adminPassword) {
throw new UnauthorizedException('Invalid or missing password');
}
if (!userId || !message) {
// Maybe Throw BadRequestException?
// For now just return failure or throw
throw new UnauthorizedException('Missing userId or message');
}
await this.botService.sendAdminMessage(Number(userId), message);
return { success: true };
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
import { BotModule } from '../bot/bot.module';
@Module({
imports: [BotModule],
controllers: [AdminController],
})
export class AdminModule { }

View File

@@ -5,6 +5,7 @@ import { UsersModule } from './users/users.module';
import { QuotesModule } from './quotes/quotes.module'; import { QuotesModule } from './quotes/quotes.module';
import { BotModule } from './bot/bot.module'; import { BotModule } from './bot/bot.module';
import { SchedulerModule } from './scheduler/scheduler.module'; import { SchedulerModule } from './scheduler/scheduler.module';
import { AdminModule } from './admin/admin.module';
@Module({ @Module({
imports: [ imports: [
@@ -16,6 +17,7 @@ import { SchedulerModule } from './scheduler/scheduler.module';
QuotesModule, QuotesModule,
BotModule, BotModule,
SchedulerModule, SchedulerModule,
AdminModule,
], ],
}) })
export class AppModule { } export class AppModule { }

View File

@@ -5,5 +5,6 @@ import { UsersModule } from '../users/users.module';
@Module({ @Module({
imports: [UsersModule], imports: [UsersModule],
providers: [BotService], providers: [BotService],
exports: [BotService],
}) })
export class BotModule { } export class BotModule { }

View File

@@ -1,10 +1,13 @@
import { Update, Ctx, Start, Help, On, Message } from 'nestjs-telegraf'; import { Update, Ctx, Start, Help, On, Message, Command, Action, InjectBot } from 'nestjs-telegraf';
import { Context } from 'telegraf'; import { Context, Telegraf, Markup } from 'telegraf';
import { UsersService } from '../users/users.service'; import { UsersService } from '../users/users.service';
@Update() @Update()
export class BotService { export class BotService {
constructor(private readonly usersService: UsersService) { } constructor(
private readonly usersService: UsersService,
@InjectBot() private readonly bot: Telegraf<Context>
) { }
@Start() @Start()
async start(@Ctx() ctx: Context) { async start(@Ctx() ctx: Context) {
@@ -13,14 +16,38 @@ export class BotService {
this.usersService.create({ this.usersService.create({
id: user.id, id: user.id,
fullName: `${user.first_name} ${user.last_name || ''}`.trim(), fullName: `${user.first_name} ${user.last_name || ''}`.trim(),
createdAt: new Date().toISOString(),
}); });
await ctx.reply(`Welcome, ${user.first_name}! You have been registered for motivational quotes.`); await ctx.reply('Приветствую тебя, мой дорогой друг. Я бот, который будет писать тебе мотивирующие цитаты. Сейчас цитаты буду приходит один раз в час, в настройках можно изменить это время.');
} }
} }
@Help() @Help()
async help(@Ctx() ctx: Context) { async help(@Ctx() ctx: Context) {
await ctx.reply('I will send you a motivational quote every 3 hours.'); await ctx.reply('Я буду присылать тебе мотивирующие цитаты. Используй /settings чтобы изменить частоту.');
}
@Command('settings')
async settings(@Ctx() ctx: Context) {
await ctx.reply('Выберите частоту получения цитат:', Markup.inlineKeyboard([
[Markup.button.callback('1 час', 'frequency_1'), Markup.button.callback('3 часа', 'frequency_3')],
[Markup.button.callback('5 часов', 'frequency_5'), Markup.button.callback('7 часов', 'frequency_7')],
[Markup.button.callback('9 часов', 'frequency_9'), Markup.button.callback('12 часов', 'frequency_12')],
]));
}
@Action(/^frequency_(\d+)$/)
async onFrequencySelect(@Ctx() ctx: Context & { match: RegExpExecArray }) {
const user = ctx.from;
if (!user) return;
const hours = parseInt(ctx.match[1]);
this.usersService.update(user.id, { frequency: hours });
await ctx.answerCbQuery();
await ctx.editMessageText(`Отлично! Теперь я буду присылать цитаты каждые ${hours} ч.`);
}
async sendAdminMessage(userId: number, message: string) {
await this.bot.telegram.sendMessage(userId, message);
} }
} }

View File

@@ -1,612 +1,33 @@
[ [
{ {
"text": "Единственный способ делать великие дела — любить то, что вы делаете.", "text": "Сила не в том, чтобы не падать, а в том, чтобы подниматься."
"author": "Стив Джобс"
},
{
"text": "Верь, что ты можешь, и ты уже прошел половину пути.",
"author": "Теодор Рузвельт"
},
{
"text": "Не важно, как медленно ты идешь, главное — не останавливаться.",
"author": "Конфуций"
},
{
"text": "Твое время ограничено, не трать его, живя чужой жизнью.",
"author": "Стив Джобс"
},
{
"text": "Лучший способ предсказать будущее — создать его.",
"author": "Питер Друкер"
},
{
"text": "Успех — это не финал, неудача — это не фатально: главное — это мужество продолжать.",
"author": "Уинстон Черчилль"
},
{
"text": "Трудности часто готовят обычных людей к необычной судьбе.",
"author": "К.С. Льюис"
},
{
"text": "Верь в себя и во все, что ты есть. Знайте, что внутри вас есть что-то большее, чем любое препятствие.",
"author": "Кристиан Д. Ларсон"
},
{
"text": "Не смотри на часы; делай то, что делают они. Продолжай идти.",
"author": "Сэм Левенсон"
},
{
"text": "Никогда не поздно поставить новую цель или обрести новую мечту.",
"author": "К.С. Льюис"
},
{
"text": "Ты сильнее, чем ты думаешь."
},
{
"text": "Все будет хорошо. Просто дыши."
},
{
"text": "Ты справляешься лучше, чем тебе кажется."
},
{
"text": "Не забывай заботиться о себе."
},
{
"text": "Твои чувства важны."
},
{
"text": "Это всего лишь плохой день, а не плохая жизнь."
},
{
"text": "Ты заслуживаешь счастья и покоя."
},
{
"text": "Маленькие шаги тоже ведут к цели."
},
{
"text": "Позволь себе отдохнуть."
},
{
"text": "Ты не одинок в своих переживаниях."
},
{
"text": "Счастье можно найти даже в самые темные времена, если не забывать обращаться к свету.",
"author": "Альбус Дамблдор"
},
{
"text": "Неудача — это просто возможность начать снова, но уже более мудро.",
"author": "Генри Форд"
},
{
"text": "Наша величайшая слава не в том, чтобы никогда не падать, а в том, чтобы подниматься каждый раз, когда мы падаем.",
"author": "Конфуций"
},
{
"text": "Ты — автор своей жизни. Пиши так, как хочешь ты."
},
{
"text": "Даже самая темная ночь закончится и взойдет солнце.",
"author": "Виктор Гюго"
},
{
"text": "Сделай сегодня что-то, за что твое будущее «я» скажет тебе спасибо."
},
{
"text": "Ошибки — это доказательство того, что ты пытаешься."
},
{
"text": "Будь собой, все остальные роли уже заняты.",
"author": "Оскар Уайльд"
},
{
"text": "Секрет того, чтобы добиться чего-то — начать.",
"author": "Марк Твен"
},
{
"text": "Ты важен. Твоя жизнь имеет значение."
},
{
"text": "Не позволяй вчерашнему дню занимать слишком много сегодняшнего.",
"author": "Уилл Роджерс"
},
{
"text": "Жизнь — это то, что с тобой происходит, пока ты строишь другие планы.",
"author": "Джон Леннон"
},
{
"text": "Через год ты пожалеешь, что не начал сегодня.",
"author": "Карен Лэмб"
},
{
"text": "Не бойся отказаться от хорошего, чтобы пойти за великим.",
"author": "Джон Д. Рокфеллер"
},
{
"text": "Я не потерпел неудачу. Я просто нашел 10 000 способов, которые не работают.",
"author": "Томас Эдисон"
},
{
"text": "Если ты проходишь через ад, продолжай идти.",
"author": "Уинстон Черчилль"
},
{
"text": "Никто не может заставить вас почувствовать себя неполноценным без вашего согласия.",
"author": "Элеонора Рузвельт"
},
{
"text": "Лучшая месть — это огромный успех.",
"author": "Фрэнк Синатра"
},
{
"text": "Ты можешь все, если поверишь в это."
},
{
"text": "Помни, зачем ты начал."
}, },
{ {
"text": "Каждый день — это новый шанс." "text": "Каждый день — это новый шанс."
}, },
{ {
"text": "Будь добр к себе." "text": "Ты сильнее, чем ты думаешь."
},
{
"text": "Твой потенциал безграничен."
},
{
"text": "Сфокусируйся на прогрессе, а не на совершенстве."
},
{
"text": "Ты делаешь мир лучше просто тем, что ты в нем есть."
},
{
"text": "Не сравнивай свое начало с чьей-то серединой."
},
{
"text": "Вдох-выдох. Ты справишься."
},
{
"text": "Иногда нужно просто остановиться и посмотреть, как далеко ты уже зашел."
},
{
"text": "Твоя улыбка может изменить чей-то день."
},
{
"text": "Не бойся просить о помощи. Это признак силы, а не слабости."
},
{
"text": "Жизнь на 10% состоит из того, что с нами происходит, и на 90% из того, как мы на это реагируем.",
"author": "Чарльз Свиндолл"
},
{
"text": "Ограничения живут только в нашей голове. Но если мы используем наше воображение, наши возможности становятся безграничными.",
"author": "Джейми Паолинетти"
},
{
"text": "Логика приведет вас из пункта А в пункт Б. Воображение приведет вас куда угодно.",
"author": "Альберт Эйнштейн"
},
{
"text": "Не ждите. Время никогда не будет «самым подходящим».",
"author": "Наполеон Хилл"
},
{
"text": "Все, что вы можете представить — реально.",
"author": "Пабло Пикассо"
},
{
"text": "Стремитесь не к успеху, а к ценностям, которые он дает.",
"author": "Альберт Эйнштейн"
},
{
"text": "Два самых важных дня в твоей жизни: день, когда ты появился на свет, и день, когда понял зачем.",
"author": "Марк Твен"
},
{
"text": "Начинай там, где ты есть. Используй то, что у тебя есть. Делай то, что можешь.",
"author": "Артур Эш"
},
{
"text": "Падать — часть жизни, подниматься — ее суть."
},
{
"text": "Ты — это не твои ошибки."
},
{
"text": "Сегодня — отличный день, чтобы начать."
},
{
"text": "Твоя скорость не имеет значения, вперед — есть вперед."
},
{
"text": "Поверь в магию новых начал."
},
{
"text": "Ты достоин любви и уважения."
},
{
"text": "Слушай свое сердце."
},
{
"text": "Ты — уникален, и это твоя суперсила."
},
{
"text": "Не позволяй страху решать за тебя."
},
{
"text": "Каждая проблема — это замаскированная возможность.",
"author": "Джон Адамс"
},
{
"text": "Счастье — это не что-то готовое. Оно происходит от ваших собственных действий.",
"author": "Далай Лама"
},
{
"text": "Чтобы дойти до цели, надо прежде всего идти.",
"author": "Оноре де Бальзак"
},
{
"text": "Великие дела нужно совершать, а не обдумывать их бесконечно.",
"author": "Юлий Цезарь"
},
{
"text": "Победа над самим собой — единственная торжественная победа.",
"author": "Платон"
},
{
"text": "Кто хочет — ищет возможности, кто не хочет — ищет причины.",
"author": "Сократ"
},
{
"text": "Жизнь прекрасна, когда ты создаешь ее сам.",
"author": "Софи Марсо"
},
{
"text": "Не бойтесь расти медленно, бойтесь оставаться неизменными.",
"author": "Китайская пословица"
},
{
"text": "Лучшее время, чтобы посадить дерево, было 20 лет назад. Следующее лучшее время — сегодня.",
"author": "Китайская пословица"
},
{
"text": "Ты можешь изменить свой мир, изменив свои мысли."
},
{
"text": "Твоя энергия — это твоя валюта. Трать ее мудро."
},
{
"text": "Будь причиной, по которой кто-то сегодня улыбнется."
},
{
"text": "Не забывай хвалить себя за маленькие победы."
},
{
"text": "Ты — достаточно хорош."
},
{
"text": "Мир нуждается в том, что можешь дать только ты."
},
{
"text": "Свет есть даже в самой темной комнате, если зажечь свечу."
},
{
"text": "Твоя жизнь — это твое искусство."
},
{
"text": "Не позволяй никому тушить твой свет."
},
{
"text": "Ты сильнее любой бури."
},
{
"text": "Вселенная любит тебя."
},
{
"text": "Доверяй процессу."
},
{
"text": "Ты находишься именно там, где должен быть."
},
{
"text": "Отпусти то, что ты не можешь контролировать."
},
{
"text": "Каждый закат — это доказательство того, что финалы тоже могут быть красивыми."
},
{
"text": "Ты — чудо."
},
{
"text": "Люби себя в первую очередь."
},
{
"text": "Ты заслуживаешь всего самого лучшего."
},
{
"text": "Не сдавайся. Чудеса случаются каждый день."
},
{
"text": "Твоя история важна."
},
{
"text": "Будь смелым."
},
{
"text": "Мечтай по-крупному."
},
{
"text": "Ты — вдохновение."
},
{
"text": "Делай то, что можешь, с тем, что имеешь, там, где ты есть.",
"author": "Теодор Рузвельт"
},
{
"text": "Гений — это 1% таланта и 99% труда.",
"author": "Томас Эдисон"
},
{
"text": "Неудача — это приправа, которая придает успеху его вкус.",
"author": "Трумен Капоте"
},
{
"text": "Если вы думаете, что вы способны на что-то, вы правы; если вы думаете, что у вас ничего не получится, вы тоже правы.",
"author": "Генри Форд"
},
{
"text": "Лучший способ взяться за что-то — перестать говорить и начать делать.",
"author": "Уолт Дисней"
},
{
"text": "Пессимист видит трудность в каждой возможности; оптимист видит возможность в каждой трудности.",
"author": "Уинстон Черчилль"
},
{
"text": "Не позволяйте шуму чужих мнений заглушить ваш внутренний голос.",
"author": "Стив Джобс"
},
{
"text": "То, что нас не убивает, делает нас сильнее.",
"author": "Фридрих Ницше"
},
{
"text": "Будьте переменой, которую вы хотите видеть в мире.",
"author": "Махатма Ганди"
},
{
"text": "Счастье не зависит от того, кто вы есть или что у вас есть; оно зависит исключительно от того, что вы думаете.",
"author": "Дейл Карнеги"
},
{
"text": "Единственный человек, которым вы должны стараться быть, — это тот, кем вы были вчера.",
"author": "Зигмунд Фрейд"
},
{
"text": "Жизнь — это 10% того, что с нами происходит, и 90% того, как мы на это реагируем.",
"author": "Чарльз Свиндолл"
},
{
"text": "Ваше время ограничено, не тратьте его, живя чужой жизнью.",
"author": "Стив Джобс"
},
{
"text": "Сложнее всего начать действовать, все остальное зависит только от упорства.",
"author": "Амелия Эрхарт"
},
{
"text": "Жизнь — это либо дерзкое приключение, либо ничего.",
"author": "Хелен Келлер"
},
{
"text": "Вы никогда не пересечете океан, если не наберетесь смелости потерять из виду берег.",
"author": "Христофор Колумб"
},
{
"text": "Два самых важных дня в твоей жизни: день, когда ты родился, и день, когда понял, зачем.",
"author": "Марк Твен"
},
{
"text": "Человек, который никогда не совершал ошибок, никогда не пробовал ничего нового.",
"author": "Альберт Эйнштейн"
},
{
"text": "Образование — это самое мощное оружие, которое вы можете использовать, чтобы изменить мир.",
"author": "Нельсон Мандела"
},
{
"text": "Секрет успеха — это знать то, что никто другой не знает.",
"author": "Аристотель Онассис"
},
{
"text": "Не бойтесь совершенства. Вам его никогда не достичь.",
"author": "Сальвадор Дали"
},
{
"text": "Успех — это способность идти от неудачи к неудаче, не теряя энтузиазма.",
"author": "Уинстон Черчилль"
},
{
"text": "Мечты не работают, пока не работаешь ты."
},
{
"text": "Ты способен на большее, чем можешь себе представить."
},
{
"text": "Каждый шаг вперед приближает тебя к цели."
},
{
"text": "Не останавливайся, пока не будешь гордиться собой."
},
{
"text": "Твоя настойчивость окупится."
},
{
"text": "Верь в свою интуицию."
},
{
"text": "Ты — кузнец своего счастья."
},
{
"text": "Не бойся быть новичком."
},
{
"text": "Все великое начинается с малого."
},
{
"text": "Твоя жизнь — это твой холст."
},
{
"text": "Рискуй, или упустишь шанс."
},
{
"text": "Слушай себя."
},
{
"text": "Ты — свет."
},
{
"text": "Не позволяй никому говорить, что ты не можешь."
},
{
"text": "Твоя сила внутри тебя."
},
{
"text": "Будь благодарен за каждый день."
},
{
"text": "Ищи радость в мелочах."
},
{
"text": "Ты — подарок этому миру."
},
{
"text": "Не бойся перемен."
},
{
"text": "Все происходит вовремя."
},
{
"text": "Ты — хозяин своей судьбы."
},
{
"text": "Не оглядывайся назад, ты идешь не туда."
},
{
"text": "Твоя улыбка — твое оружие."
},
{
"text": "Будь добрым, это всегда модно."
},
{
"text": "Ты — вдохновение для других."
}, },
{ {
"text": "Не бойся мечтать." "text": "Не бойся мечтать."
}, },
{ {
"text": "Твои мечты важны." "text": "Маленькие шаги ведут к большим целям."
}, },
{ {
"text": "Ты — уникален." "text": "Все в твоих руках."
}, },
{ {
"text": "Люби жизнь." "text": "Верь в процесс."
}, },
{ {
"text": "Живи моментом." "text": "Ты уникален."
}, },
{ {
"text": "Ты — достаточно сильный." "text": "Никогда не сдавайся."
}, },
{ {
"text": "Не сдавайся перед трудностями." "text": "Будь лучшей версией себя."
},
{
"text": "Ты — победитель."
},
{
"text": "Верь в чудеса."
},
{
"text": "Ты — любовь."
},
{
"text": "Свети ярко."
},
{
"text": "Ты — надежда."
},
{
"text": "Будь собой."
},
{
"text": "Ты — радость."
},
{
"text": "Не бойся быть счастливым."
},
{
"text": "Ты — мир."
},
{
"text": "Все будет хорошо."
},
{
"text": "Ты — гармония."
},
{
"text": "Люби себя."
},
{
"text": "Ты — красота."
},
{
"text": "Не бойся жить."
},
{
"text": "Ты — свобода."
},
{
"text": "Ты — мудрость."
},
{
"text": "Ты — сила."
},
{
"text": "Ты — добро."
},
{
"text": "Ты — свет."
},
{
"text": "Ты — любовь."
},
{
"text": "Ты — жизнь."
},
{
"text": "Ты — вселенная."
},
{
"text": "Ты — бесконечность."
},
{
"text": "Ты — вечность."
},
{
"text": "Ты — сейчас."
},
{
"text": "Ты — здесь."
},
{
"text": "Ты — есть."
},
{
"text": "Просто будь."
},
{
"text": "Дыши."
}, },
{ {
"text": "Живи." "text": "Живи."
@@ -673,5 +94,20 @@
}, },
{ {
"text": "Будь." "text": "Будь."
},
{
"text": "Дорогу осилит идущий."
},
{
"text": "Секрет успеха — просто начать."
},
{
"text": "Ошибки — это доказательство того, что ты пытаешься."
},
{
"text": "Самое темное время — перед рассветом."
},
{
"text": "Действуй сегодня."
} }
] ]

View File

@@ -1,8 +1,8 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule'; import { Cron } from '@nestjs/schedule';
import { InjectBot } from 'nestjs-telegraf'; import { InjectBot } from 'nestjs-telegraf';
import { Context, Telegraf } from 'telegraf'; import { Context, Telegraf } from 'telegraf';
import { UsersService } from '../users/users.service'; import { UsersService, User } from '../users/users.service';
import { QuotesService } from '../quotes/quotes.service'; import { QuotesService } from '../quotes/quotes.service';
@Injectable() @Injectable()
@@ -15,39 +15,56 @@ export class SchedulerService {
@InjectBot() private readonly bot: Telegraf<Context>, @InjectBot() private readonly bot: Telegraf<Context>,
) { } ) { }
// Run every 3 hours // Run every hour
@Cron('0 */3 * * *') @Cron('0 * * * *')
handleCron() { async handleCron() {
this.logger.debug('Cron job triggered. Scheduling random quote...'); this.logger.debug('Hourly cron triggered. Checking users...');
// Random delay between 0 and 3 hours (in milliseconds)
// 3 hours = 3 * 60 * 60 * 1000 = 10800000 ms
// We'll use slightly less than 3 hours to avoid overlap, e.g., 2.5 hours
const maxDelay = 2.5 * 60 * 60 * 1000;
const delay = Math.floor(Math.random() * maxDelay);
this.logger.debug(`Quote scheduled in ${delay / 1000 / 60} minutes.`);
setTimeout(() => {
this.sendQuoteToAllUsers();
}, delay);
}
private async sendQuoteToAllUsers() {
this.logger.debug('Sending quotes to all users...');
const users = this.usersService.findAll(); const users = this.usersService.findAll();
const quote = this.quotesService.getRandomQuote(); const now = new Date();
let message = `"${quote.text}"`;
if (quote.author) {
message += `\n\n- ${quote.author}`;
}
for (const user of users) { for (const user of users) {
try { await this.checkAndSendQuote(user, now);
await this.bot.telegram.sendMessage(user.id, message); }
} catch (error) { }
this.logger.error(`Failed to send quote to user ${user.id}: ${error.message}`);
private async checkAndSendQuote(user: User, now: Date) {
const lastSent = user.lastQuoteSentAt ? new Date(user.lastQuoteSentAt) : new Date(0);
const diffMs = now.getTime() - lastSent.getTime();
const diffHours = diffMs / (1000 * 60 * 60);
// Allow a small buffer for timing execution, e.g. if diff is 0.99 hours but frequency is 1?
// Cron is exact hour. If last sent was 10:00:05 and now is 11:00:00. diff is slightly less than 1.
// But we update `lastQuoteSentAt` to `now` (execution time).
// If we update to `now`, then next time it will be exact hour difference.
// Let's use strict >= logic but maybe `frequency - 0.1`?
// Actually, if we update `lastQuoteSentAt` to `now.toISOString()` which is the cron time approx.
// It should be fine. If we missed by a second, it waits another hour.
// User won't mind waiting 1 more hour initially or we can be generous.
// Let's stick to `>= user.frequency`.
// Initial user `lastQuoteSentAt` is registration time.
// If reg at 10:30. Cron at 11:00. Diff 0.5. No quote.
// Cron at 12:00. Diff 1.5. Quote! (if freq 1).
// Seems correct usage.
if (diffHours >= user.frequency) {
await this.sendQuote(user);
// Updating user lastQuoteSentAt to now
this.usersService.update(user.id, { lastQuoteSentAt: now.toISOString() });
}
}
private async sendQuote(user: User) {
try {
const quote = this.quotesService.getRandomQuote();
let message = `"${quote.text}"`;
if (quote.author) {
message += `\n\n- ${quote.author}`;
} }
await this.bot.telegram.sendMessage(user.id, message);
} catch (error) {
this.logger.error(`Failed to send quote to user ${user.id}: ${error.message}`);
} }
} }
} }

View File

@@ -6,6 +6,8 @@ export interface User {
id: number; id: number;
fullName: string; fullName: string;
createdAt: string; createdAt: string;
frequency: number; // in hours, default 1
lastQuoteSentAt: string; // ISO string
} }
@Injectable() @Injectable()
@@ -17,10 +19,19 @@ export class UsersService {
return []; return [];
} }
const data = fs.readFileSync(this.filePath, 'utf8'); const data = fs.readFileSync(this.filePath, 'utf8');
return JSON.parse(data); try {
return JSON.parse(data);
} catch (e) {
return [];
}
} }
private writeUsers(users: User[]): void { private writeUsers(users: User[]): void {
// Ensure directory exists
const dir = path.dirname(this.filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(this.filePath, JSON.stringify(users, null, 2), 'utf8'); fs.writeFileSync(this.filePath, JSON.stringify(users, null, 2), 'utf8');
} }
@@ -28,11 +39,34 @@ export class UsersService {
return this.readUsers(); return this.readUsers();
} }
create(user: User): void { findOne(id: number): User | undefined {
return this.readUsers().find((u) => u.id === id);
}
create(user: Partial<User> & { id: number; fullName: string }): void {
const users = this.readUsers(); const users = this.readUsers();
const existingUser = users.find((u) => u.id === user.id); const existingUser = users.find((u) => u.id === user.id);
const newUser: User = {
...user,
createdAt: user.createdAt || new Date().toISOString(),
frequency: user.frequency || 1,
lastQuoteSentAt: user.lastQuoteSentAt || new Date().toISOString(),
};
if (!existingUser) { if (!existingUser) {
users.push(user); users.push(newUser);
this.writeUsers(users);
} else {
// Option: update existing user if re-registering?
// For now, assume id persistence.
}
}
update(id: number, updateData: Partial<User>): void {
const users = this.readUsers();
const index = users.findIndex(u => u.id === id);
if (index !== -1) {
users[index] = { ...users[index], ...updateData };
this.writeUsers(users); this.writeUsers(users);
} }
} }