Initial commit

This commit is contained in:
Zwuck
2025-12-07 16:25:19 +05:00
commit 73278ffe53
29 changed files with 11453 additions and 0 deletions

1
.env.example Normal file
View File

@@ -0,0 +1 @@
BOT_TOKEN=YOUR_BOT_TOKEN_HERE

25
.eslintrc.js Normal file
View File

@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

56
.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

4
.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

13
Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["npm", "run", "start:prod"]

99
README.md Normal file
View File

@@ -0,0 +1,99 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Project setup
```bash
$ npm install
```
## Compile and run the project
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Run tests
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Deployment
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
$ npm install -g mau
$ mau deploy
```
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
## Resources
Check out a few resources that may come in handy when working with NestJS:
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

12
docker-compose.yml Normal file
View File

@@ -0,0 +1,12 @@
version: '3.8'
services:
app:
build: .
container_name: mental_health_bot
restart: always
volumes:
- ./src/data:/app/dist/data
- ./src/quotes.json:/app/dist/quotes.json
env_file:
- .env

11
nest-cli.json Normal file
View File

@@ -0,0 +1,11 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"assets": [
"**/*.json"
]
}
}

10168
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

74
package.json Normal file
View File

@@ -0,0 +1,74 @@
{
"name": "mental_health",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^6.0.1",
"@prisma/client": "^6.19.0",
"nestjs-telegraf": "^2.9.1",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"telegraf": "^4.16.3"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^8.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"prisma": "^6.19.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

12
src/app.controller.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

21
src/app.module.ts Normal file
View File

@@ -0,0 +1,21 @@
import { Module } from '@nestjs/common';
import { TelegrafModule } from 'nestjs-telegraf';
import { ScheduleModule } from '@nestjs/schedule';
import { UsersModule } from './users/users.module';
import { QuotesModule } from './quotes/quotes.module';
import { BotModule } from './bot/bot.module';
import { SchedulerModule } from './scheduler/scheduler.module';
@Module({
imports: [
ScheduleModule.forRoot(),
TelegrafModule.forRoot({
token: process.env.BOT_TOKEN || 'YOUR_BOT_TOKEN_HERE',
}),
UsersModule,
QuotesModule,
BotModule,
SchedulerModule,
],
})
export class AppModule { }

8
src/app.service.ts Normal file
View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

9
src/bot/bot.module.ts Normal file
View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { BotService } from './bot.service';
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule],
providers: [BotService],
})
export class BotModule { }

26
src/bot/bot.service.ts Normal file
View File

@@ -0,0 +1,26 @@
import { Update, Ctx, Start, Help, On, Message } from 'nestjs-telegraf';
import { Context } from 'telegraf';
import { UsersService } from '../users/users.service';
@Update()
export class BotService {
constructor(private readonly usersService: UsersService) { }
@Start()
async start(@Ctx() ctx: Context) {
const user = ctx.from;
if (user) {
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.`);
}
}
@Help()
async help(@Ctx() ctx: Context) {
await ctx.reply('I will send you a motivational quote every 3 hours.');
}
}

1
src/data/users.json Normal file
View File

@@ -0,0 +1 @@
[]

8
src/main.ts Normal file
View File

@@ -0,0 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

677
src/quotes.json Normal file
View File

@@ -0,0 +1,677 @@
[
{
"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": "Жизнь на 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": "Беги."
},
{
"text": "Лети."
},
{
"text": "Сияй."
},
{
"text": "Гори."
},
{
"text": "Свети."
},
{
"text": "Звучи."
},
{
"text": "Пой."
},
{
"text": "Танцуй."
},
{
"text": "Смейся."
},
{
"text": "Радуйся."
},
{
"text": "Улыбайся."
},
{
"text": "Плачь."
},
{
"text": "Чувствуй."
},
{
"text": "Будь."
}
]

View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { QuotesService } from './quotes.service';
@Module({
providers: [QuotesService],
exports: [QuotesService],
})
export class QuotesModule { }

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
export interface Quote {
text: string;
author?: string;
}
@Injectable()
export class QuotesService {
private readonly filePath = path.resolve(__dirname, '..', 'quotes.json');
private readQuotes(): Quote[] {
if (!fs.existsSync(this.filePath)) {
return [];
}
const data = fs.readFileSync(this.filePath, 'utf8');
return JSON.parse(data);
}
getRandomQuote(): Quote {
const quotes = this.readQuotes();
if (quotes.length === 0) {
return { text: 'No quotes available.', author: 'System' };
}
const randomIndex = Math.floor(Math.random() * quotes.length);
return quotes[randomIndex];
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { SchedulerService } from './scheduler.service';
import { UsersModule } from '../users/users.module';
import { QuotesModule } from '../quotes/quotes.module';
@Module({
imports: [UsersModule, QuotesModule],
providers: [SchedulerService],
})
export class SchedulerModule { }

View File

@@ -0,0 +1,53 @@
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { InjectBot } from 'nestjs-telegraf';
import { Context, Telegraf } from 'telegraf';
import { UsersService } from '../users/users.service';
import { QuotesService } from '../quotes/quotes.service';
@Injectable()
export class SchedulerService {
private readonly logger = new Logger(SchedulerService.name);
constructor(
private readonly usersService: UsersService,
private readonly quotesService: QuotesService,
@InjectBot() private readonly bot: Telegraf<Context>,
) { }
// 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...');
const users = this.usersService.findAll();
const quote = this.quotesService.getRandomQuote();
let message = `"${quote.text}"`;
if (quote.author) {
message += `\n\n- ${quote.author}`;
}
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}`);
}
}
}
}

View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule { }

View File

@@ -0,0 +1,39 @@
import { Injectable } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
export interface User {
id: number;
fullName: string;
createdAt: string;
}
@Injectable()
export class UsersService {
private readonly filePath = path.resolve(__dirname, '..', 'data', 'users.json');
private readUsers(): User[] {
if (!fs.existsSync(this.filePath)) {
return [];
}
const data = fs.readFileSync(this.filePath, 'utf8');
return JSON.parse(data);
}
private writeUsers(users: User[]): void {
fs.writeFileSync(this.filePath, JSON.stringify(users, null, 2), 'utf8');
}
findAll(): User[] {
return this.readUsers();
}
create(user: User): void {
const users = this.readUsers();
const existingUser = users.find((u) => u.id === user.id);
if (!existingUser) {
users.push(user);
this.writeUsers(users);
}
}
}

24
test/app.e2e-spec.ts Normal file
View File

@@ -0,0 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

9
test/jest-e2e.json Normal file
View File

@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

4
tsconfig.build.json Normal file
View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

21
tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}