From db535c2560b7ea83fa77258308ee2793ca102958 Mon Sep 17 00:00:00 2001 From: Zwuck Date: Tue, 16 Dec 2025 12:08:51 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F,=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B9=D0=BA=D0=B0=20=D1=87=D0=B0=D1=81=D1=82=D0=BE=D1=82=D1=8B?= =?UTF-8?q?=20=D1=86=D0=B8=D1=82=D0=B0=D1=82=20=D0=B8=20=D0=B0=D0=B4=D0=BC?= =?UTF-8?q?=D0=B8=D0=BD-=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/schema.prisma | 22 +- src/admin/admin.controller.ts | 27 ++ src/admin/admin.module.ts | 9 + src/app.module.ts | 2 + src/bot/bot.module.ts | 1 + src/bot/bot.service.ts | 39 +- src/quotes.json | 610 ++--------------------------- src/scheduler/scheduler.service.ts | 79 ++-- src/users/users.service.ts | 40 +- 9 files changed, 189 insertions(+), 640 deletions(-) create mode 100644 src/admin/admin.controller.ts create mode 100644 src/admin/admin.module.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a341204..b170839 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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 { provider = "prisma-client" output = "../generated/prisma" @@ -15,11 +9,13 @@ datasource db { } model User { - id Int @id @default(autoincrement()) // ID - telegramId BigInt @unique // ID (BigInt ) - username String? // ( ) - fio String? // ( + ) - createdAt DateTime @default(now()) // + id Int @id @default(autoincrement()) // Внутренний ID базы + telegramId BigInt @unique // ID из Телеграма (BigInt обязателен) + username String? // Никнейм (может не быть) + fio String? // ФИО (Имя + Фамилия) + createdAt DateTime @default(now()) // Дата первого обращения к боту + frequency Int @default(1) // Частота отправки цитат в часах (1, 3, 5, 7, 9, 12) + lastQuoteSentAt DateTime @default(now()) // Время последней отправки цитаты - @@map("users") // : "users" ( ) -} \ No newline at end of file + @@map("users") // Необязательно: делает имя таблицы в БД "users" (множественное число) +} diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts new file mode 100644 index 0000000..ef67852 --- /dev/null +++ b/src/admin/admin.controller.ts @@ -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 }; + } +} diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts new file mode 100644 index 0000000..d31f139 --- /dev/null +++ b/src/admin/admin.module.ts @@ -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 { } diff --git a/src/app.module.ts b/src/app.module.ts index 7a7669f..bd8f0f5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,6 +5,7 @@ import { UsersModule } from './users/users.module'; import { QuotesModule } from './quotes/quotes.module'; import { BotModule } from './bot/bot.module'; import { SchedulerModule } from './scheduler/scheduler.module'; +import { AdminModule } from './admin/admin.module'; @Module({ imports: [ @@ -16,6 +17,7 @@ import { SchedulerModule } from './scheduler/scheduler.module'; QuotesModule, BotModule, SchedulerModule, + AdminModule, ], }) export class AppModule { } diff --git a/src/bot/bot.module.ts b/src/bot/bot.module.ts index b506c02..470b64f 100644 --- a/src/bot/bot.module.ts +++ b/src/bot/bot.module.ts @@ -5,5 +5,6 @@ import { UsersModule } from '../users/users.module'; @Module({ imports: [UsersModule], providers: [BotService], + exports: [BotService], }) export class BotModule { } diff --git a/src/bot/bot.service.ts b/src/bot/bot.service.ts index 2850e88..8dda9c3 100644 --- a/src/bot/bot.service.ts +++ b/src/bot/bot.service.ts @@ -1,10 +1,13 @@ -import { Update, Ctx, Start, Help, On, Message } from 'nestjs-telegraf'; -import { Context } from 'telegraf'; +import { Update, Ctx, Start, Help, On, Message, Command, Action, InjectBot } from 'nestjs-telegraf'; +import { Context, Telegraf, Markup } from 'telegraf'; import { UsersService } from '../users/users.service'; @Update() export class BotService { - constructor(private readonly usersService: UsersService) { } + constructor( + private readonly usersService: UsersService, + @InjectBot() private readonly bot: Telegraf + ) { } @Start() async start(@Ctx() ctx: Context) { @@ -13,14 +16,38 @@ export class BotService { this.usersService.create({ id: user.id, 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() 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); } } diff --git a/src/quotes.json b/src/quotes.json index c497d59..2fd87fc 100644 --- a/src/quotes.json +++ b/src/quotes.json @@ -1,612 +1,33 @@ [ { - "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": "Жизнь на 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": "Живи." @@ -673,5 +94,20 @@ }, { "text": "Будь." + }, + { + "text": "Дорогу осилит идущий." + }, + { + "text": "Секрет успеха — просто начать." + }, + { + "text": "Ошибки — это доказательство того, что ты пытаешься." + }, + { + "text": "Самое темное время — перед рассветом." + }, + { + "text": "Действуй сегодня." } ] \ No newline at end of file diff --git a/src/scheduler/scheduler.service.ts b/src/scheduler/scheduler.service.ts index 48df4f1..32b8a2f 100644 --- a/src/scheduler/scheduler.service.ts +++ b/src/scheduler/scheduler.service.ts @@ -1,8 +1,8 @@ import { Injectable, Logger } from '@nestjs/common'; -import { Cron, CronExpression } from '@nestjs/schedule'; +import { Cron } from '@nestjs/schedule'; import { InjectBot } from 'nestjs-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'; @Injectable() @@ -15,39 +15,56 @@ export class SchedulerService { @InjectBot() private readonly bot: Telegraf, ) { } - // Run every 3 hours - @Cron('0 */3 * * *') - handleCron() { - this.logger.debug('Cron job triggered. Scheduling random quote...'); - - // 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...'); + // Run every hour + @Cron('0 * * * *') + async handleCron() { + this.logger.debug('Hourly cron triggered. Checking users...'); const users = this.usersService.findAll(); - const quote = this.quotesService.getRandomQuote(); - let message = `"${quote.text}"`; - if (quote.author) { - message += `\n\n- ${quote.author}`; - } + const now = new Date(); for (const user of users) { - try { - await this.bot.telegram.sendMessage(user.id, message); - } catch (error) { - this.logger.error(`Failed to send quote to user ${user.id}: ${error.message}`); + await this.checkAndSendQuote(user, now); + } + } + + 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}`); } } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index e01cc1f..aa395af 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -6,6 +6,8 @@ export interface User { id: number; fullName: string; createdAt: string; + frequency: number; // in hours, default 1 + lastQuoteSentAt: string; // ISO string } @Injectable() @@ -17,10 +19,19 @@ export class UsersService { return []; } const data = fs.readFileSync(this.filePath, 'utf8'); - return JSON.parse(data); + try { + return JSON.parse(data); + } catch (e) { + return []; + } } 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'); } @@ -28,11 +39,34 @@ export class UsersService { return this.readUsers(); } - create(user: User): void { + findOne(id: number): User | undefined { + return this.readUsers().find((u) => u.id === id); + } + + create(user: Partial & { id: number; fullName: string }): void { const users = this.readUsers(); 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) { - 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): void { + const users = this.readUsers(); + const index = users.findIndex(u => u.id === id); + if (index !== -1) { + users[index] = { ...users[index], ...updateData }; this.writeUsers(users); } }