diff --git a/.eslintrc b/.eslintrc index 207946d..7a0278e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,7 +16,7 @@ ], "rules": { "@typescript-eslint/consistent-type-imports": "error", - "quotes": [2, "single", { "avoidEscape": true }], + "quotes": [2, "double", { "avoidEscape": true }], "no-extra-parens": "off", "arrow-parens": "off", "prettier/prettier": 2, diff --git a/.prettierrc b/.prettierrc index 391c9f7..16a3f23 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,9 +2,7 @@ "trailingComma": "none", "tabWidth": 2, "semi": true, - "singleQuote": true, "useTabs": true, - "jsxSingleQuote": true, "bracketSpacing": true, "arrowParens": "avoid" } diff --git a/package-lock.json b/package-lock.json index e296e70..8a96d28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -842,9 +842,9 @@ } }, "node_modules/@grammyjs/types": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-2.8.2.tgz", - "integrity": "sha512-RNYVxg5+yOOeDN9PY05KGEA/82vNXd7QMQPtEMCT8neg1jl7Keg/cbUElHOU8zYQUvVChu3uYSkfkviLAxvDSg==" + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.4.4.tgz", + "integrity": "sha512-r/m54PnAb1Yh76SNlU/3uc2K9t0cYfZXiYZ6IqJfb245m7QeIH7DnjFTg/OtymybKehdkm3PFlw7QODJNyDqhQ==" }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", @@ -3290,14 +3290,14 @@ "dev": true }, "node_modules/grammy": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.10.1.tgz", - "integrity": "sha512-HFcFFq/9MsjQODGWL0EZKaamxNIQpHOzGsj0c/0mjQgtF7l1fJ8YK18A9CYWDXm7gYcJhY+jFMly+I0VN97LSw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.20.2.tgz", + "integrity": "sha512-hZ2ZIB8n9rcglpcnerqOgn+duVvB144mdbLAgKfE18Nj/ZBi+6SzXT/P5dIz89F56Q6YynzrwUFN8+uCPVuc1A==", "dependencies": { - "@grammyjs/types": "^2.8.2", + "@grammyjs/types": "3.4.4", "abort-controller": "^3.0.0", "debug": "^4.3.4", - "node-fetch": "^2.6.7" + "node-fetch": "^2.7.0" }, "engines": { "node": "^12.20.0 || >=14.13.1" @@ -4633,9 +4633,9 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz", - "integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", "dependencies": { "@types/whatwg-url": "^8.2.1", "whatwg-url": "^11.0.0" @@ -4684,9 +4684,9 @@ "dev": true }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -5801,9 +5801,9 @@ } }, "node_modules/socks": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", - "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", "dependencies": { "ip": "^2.0.0", "smart-buffer": "^4.2.0" @@ -7105,9 +7105,9 @@ } }, "@grammyjs/types": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-2.8.2.tgz", - "integrity": "sha512-RNYVxg5+yOOeDN9PY05KGEA/82vNXd7QMQPtEMCT8neg1jl7Keg/cbUElHOU8zYQUvVChu3uYSkfkviLAxvDSg==" + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.4.4.tgz", + "integrity": "sha512-r/m54PnAb1Yh76SNlU/3uc2K9t0cYfZXiYZ6IqJfb245m7QeIH7DnjFTg/OtymybKehdkm3PFlw7QODJNyDqhQ==" }, "@humanwhocodes/config-array": { "version": "0.11.11", @@ -8827,14 +8827,14 @@ "dev": true }, "grammy": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.10.1.tgz", - "integrity": "sha512-HFcFFq/9MsjQODGWL0EZKaamxNIQpHOzGsj0c/0mjQgtF7l1fJ8YK18A9CYWDXm7gYcJhY+jFMly+I0VN97LSw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.20.2.tgz", + "integrity": "sha512-hZ2ZIB8n9rcglpcnerqOgn+duVvB144mdbLAgKfE18Nj/ZBi+6SzXT/P5dIz89F56Q6YynzrwUFN8+uCPVuc1A==", "requires": { - "@grammyjs/types": "^2.8.2", + "@grammyjs/types": "3.4.4", "abort-controller": "^3.0.0", "debug": "^4.3.4", - "node-fetch": "^2.6.7" + "node-fetch": "^2.7.0" } }, "graphemer": { @@ -9763,9 +9763,9 @@ } }, "mongodb-connection-string-url": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz", - "integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", "requires": { "@types/whatwg-url": "^8.2.1", "whatwg-url": "^11.0.0" @@ -9807,9 +9807,9 @@ "dev": true }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "requires": { "whatwg-url": "^5.0.0" } @@ -10563,9 +10563,9 @@ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, "socks": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", - "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", "requires": { "ip": "^2.0.0", "smart-buffer": "^4.2.0" diff --git a/package.json b/package.json index 683f164..02f3a42 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "compile": "tsc", "postcompile": "cp -r src/locales dist", - "bg:start": "npm run prepare && pm2 start dist/index.js --name propertygram-bot", - "bg:restart": "npm run prepare && pm2 restart telegram-bot", + "bg:start": "npm run prepare && pm2 start dist/index.js --name propertygram-bot -f", + "bg:restart": "npm run prepare && pm2 restart propertygram-bot", "bg:logs": "pm2 logs telegram-bot --lines 1000 -f", "start": "npm run compile && node .", "lint": "npx prettier --write src", diff --git a/propertiesSampleData.json b/propertiesSampleData.json new file mode 100644 index 0000000..29fb20a --- /dev/null +++ b/propertiesSampleData.json @@ -0,0 +1,20 @@ +{ + "collection": "SaliSol Hills", + "name": "Villa 6", + "price": 670000, + "availability": true, + "videoFileId": "https://www.dropbox.com/sh/t6330gcog0lz7dh/AADqYOkfEnvIQ0RF6Lr0eRBja?dl=0&preview=Video+1+SaliSol+Hills+Showhouse+2023.mp4", + "thumbnailUrl": "https://uc470239d7d0c1c41f915323d5c8.previews.dropboxusercontent.com/p/thumb/ACLJ7D6O6bqDe2gRpcdVMDPaxNwZYWAenzJD8IQuZKGoaWdojbcZHMHHw0X-6CUmisfriGocNXvxH1tLSty6kEh3PftuhgeMPVUlQuhD7KmGYWfmqnWQKPY9bwnKjU-qxJJv7c2StyA_xn6H4l4MCTF0dWVHalbKeTT5aW5ucsm5f6raAcqCXx7mRQto9AkM3lHzDi8dw7imp65me_EFKUmEziFK108IecRwKQ1KZdNWrjVIuAcaIyGzq51l--Kphv6rh9tHaSXx0DMv_MX0vtYbL751cve0h9YGrvGPnFUMKhP6oPqXAFzLnSJCc3qnbzfaGuaWeI7sjBLh5-MXtt8JTZfsU8-om8Kv1k0gtPF4dg/p.png", + "builtMetersSquared": 168, + "plotMetersSquared": 532, + "dropboxUrl": "https://www.dropbox.com/sh/t6330gcog0lz7dh/AADqYOkfEnvIQ0RF6Lr0eRBja?dl=0", + "telegramContactUrl": "https://t.me/PropertyGram_bot", + "websiteUrl": "https://salisolpark.com/salisol-hills", + "albumUrls": [ + "https://uc470239d7d0c1c41f915323d5c8.previews.dropboxusercontent.com/p/thumb/ACLJ7D6O6bqDe2gRpcdVMDPaxNwZYWAenzJD8IQuZKGoaWdojbcZHMHHw0X-6CUmisfriGocNXvxH1tLSty6kEh3PftuhgeMPVUlQuhD7KmGYWfmqnWQKPY9bwnKjU-qxJJv7c2StyA_xn6H4l4MCTF0dWVHalbKeTT5aW5ucsm5f6raAcqCXx7mRQto9AkM3lHzDi8dw7imp65me_EFKUmEziFK108IecRwKQ1KZdNWrjVIuAcaIyGzq51l--Kphv6rh9tHaSXx0DMv_MX0vtYbL751cve0h9YGrvGPnFUMKhP6oPqXAFzLnSJCc3qnbzfaGuaWeI7sjBLh5-MXtt8JTZfsU8-om8Kv1k0gtPF4dg/p.png", + "https://ucd3b2b2169690f13f7943ad683e.previews.dropboxusercontent.com/p/thumb/ACK1cGb1225TlUB5zrve84aiZ03Wx-1D7ic-toemLMfr70PYmbUBZTH073DcYCAjm9GiO5PcOf_4zl7fRaasaRcQQpiTsvXvd6qjROClxeyZ9Z40yil08MzpFMz0IHtScRDg6cPA2ppVRD_DlNfPhFj-OfBc3MX3d5kJOQPZs9xnJWn5PrIhJ48dhAycsox1PuRe2pFV0p5-SfftP0VZjkDSr2Hd50R9n0s34J_boekj_cUy68YI4lmGBlpNpdW44e1fMoLDZmwuV1owDdSotaqGDxUD9U03isNh69FfVpinLzw9y34gcHozq0xZ-VextAtrtrt2bbaQMuJDI-N7t42qGYbMKOEA_uaZYekRDCeJAg/p.png", + "https://ucbda0eb239a53969d8a8e7f5475.previews.dropboxusercontent.com/p/thumb/ACLqnVpH6F7yEf4Nlojv_K5vcRsq2MF8n-mRdf5IUMXsv3_Y5poMtlbty1VBRyg214Wqsyh-JjFUv_6tk1h5pe8Dn3c6BwqydRNKYW9o4gO0aMzvq3G13DZ-y1hkPFifyAzYlG_ibSv0Xmrcwvv5kAzs9raGFhe7dMMLm0j4oVbThEgDVOcVRRkLyp0mQl825lH-v57ZP_J71izkP_Dxdi3hyUWxp2FuOq7ZtWGJuub5S7uBmxiSjenOzBe0PNthPGjoG5K3sJFr_KynvwRtYXummX8APLR5ezOWf2M8mNcXRcOI2nH7DcyzZIYgsVlsaxCs-oS2I89wAOLog_PcJB4eqGvmbBe5R0iW8bWQd0c6GA/p.jpeg", + "https://uc177e23be7b8c96b07f3e30647c.previews.dropboxusercontent.com/p/thumb/ACIFhDABAj7th24mAYq_nS0PzZSnryTlkBQL_74SBQf2mIFgbBsn-ZI_s-dLHAqtdkAJZxIGq0NZZ0XGeW2aRR9MN6OaJZ8xU5VLnFW_57kNtaOgasmmtl13Jg9SdFeu_SaEh2w2sDfqeieB4vJlapSkGqQaVZgv7p7uyNqf39g5gce2rnxvSCuTocQs52hpfWqlvXNT35XUb5Tw2f-wx6MZXFWX0cVPI5FxVAyPV2EDgFdE_rgSQ0vPaCENjJAix7WXajPI2Df8tCXntoSBEY8gDZYy4T7Maz_kjBSR5euIgKngdRLF2iHtLwgIbSfQyKxGxm2x3crjUJv8zmRCNRFVSxiJL_nJzySHumYgGxzO_g/p.jpeg", + "https://ucdc0769904db036def229337527.previews.dropboxusercontent.com/p/thumb/ACKJuGcDqfCB039AMybN4Lbog87xyqBblw-rnUyll17XVCZRHe-P-6r81amFKJEU-CX5hsgP4fJQo-9nl5OOqq3c_DN5xsPG2p4TKPvhmkDfzgNQ_ok78aroz4JaWsybLknnE3mbr7Yc2jbx88Ocnp1Z37rLi3NPbjoJKCJzOnb-OlKKPAa-tLZDP4Sy3xxsXzD6ENaRmFQSqtDw4dPIqouajTChREApsVCVEyz6aXNw4YryfScBGOmWSMpOiAZp7BAOOAPIDGBqSlvVJQ0pTkLOXKdXqlk14S1W9o2q5X8c7V425Em7nMH8ie3N0j_y0XfCpVSepKUVJBYofn2sY5BV/p.jpeg" + ] +} diff --git a/src/config/app.ts b/src/config/app.ts index a2a1e1d..6da1c3f 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -1,18 +1,18 @@ -import { loadEnv } from '../helpers/load-env.js'; -import { validateEnv } from '../helpers/validate-env.js'; -import { startBot } from './bot.js'; -import { connectToDb } from './database.js'; -import debugCreator from 'debug'; +import { loadEnv } from "../helpers/load-env.js"; +import { validateEnv } from "../helpers/validate-env.js"; +import { startBot } from "./bot.js"; +import { connectToDb } from "./database.js"; +import debugCreator from "debug"; -const debug = debugCreator('app:'); +const debug = debugCreator("app:"); export async function startApp() { - debug('Starting app...'); + debug("Starting app..."); try { loadEnv(); - validateEnv(['TELEGRAM_TOKEN', 'DB_CONNECTION_STRING']); + validateEnv(["TELEGRAM_TOKEN", "DB_CONNECTION_STRING"]); } catch (error) { - console.error('Error occurred while loading environment:', error); + console.error("Error occurred while loading environment:", error); process.exit(1); } @@ -20,14 +20,14 @@ export async function startApp() { try { database = await connectToDb(); } catch (error) { - console.error('Error occurred while connecting to the database:', error); + console.error("Error occurred while connecting to the database:", error); process.exit(2); } try { await startBot(database); } catch (error) { - console.error('Error occurred while starting the bot:', error); + console.error("Error occurred while starting the bot:", error); process.exit(3); } } diff --git a/src/config/bot.ts b/src/config/bot.ts index 140d9f3..8cfafc7 100644 --- a/src/config/bot.ts +++ b/src/config/bot.ts @@ -1,16 +1,18 @@ -import type { I18n } from '@grammyjs/i18n/dist/source/i18n.js'; -import { Bot as TelegramBot, session } from 'grammy'; +import type { I18n } from "@grammyjs/i18n/dist/source/i18n.js"; +import { Bot as TelegramBot, session } from "grammy"; -import { resolvePath } from '../helpers/resolve-path.js'; -import { createReplyWithTextFunc } from '../services/context.js'; -import type { CustomContext } from '../types/context.js'; -import type { Chat, Database } from '../types/database.js'; -import { initLocaleEngine } from './locale-engine.js'; -import { startController } from '../controllers/start.js'; -import { stopController } from '../controllers/stop.js'; -import type { Bot } from '../types/telegram.js'; -import { buildName, getOrCreatePlayer } from '../services/user.js'; -import { getOrCreateChat } from '../services/chat.js'; +import { resolvePath } from "../helpers/resolve-path.js"; +import { createReplyWithTextFunc } from "../services/context.js"; +import type { CustomContext } from "../types/context.js"; +import type { Chat, Database } from "../types/database.js"; +import { initLocaleEngine } from "./locale-engine.js"; +import { startController } from "../controllers/start.js"; +import { stopController } from "../controllers/stop.js"; +import type { Bot } from "../types/telegram.js"; +import { buildName, getOrCreatePlayer } from "../services/user.js"; +import { getOrCreateChat } from "../services/chat.js"; +import { propertiesController } from "../controllers/properties.js"; +import { fileIdController } from "../controllers/fileId.js"; function extendContext(bot: Bot, database: Database) { bot.use(async (ctx, next) => { @@ -22,7 +24,7 @@ function extendContext(bot: Bot, database: Database) { ctx.db = database; let chat: Chat | null = null; - if (ctx.chat.type !== 'private') { + if (ctx.chat.type !== "private") { chat = await getOrCreateChat({ db: database, chatId: ctx.chat.id, @@ -30,7 +32,7 @@ function extendContext(bot: Bot, database: Database) { }); } - ctx.entities = { + ctx.config = { user: await getOrCreatePlayer({ db: database, userId: ctx.from.id, @@ -52,10 +54,12 @@ function setupMiddlewares(bot: Bot, localeEngine: I18n) { function setupControllers(bot: Bot) { bot.use(startController); bot.use(stopController); + bot.use(propertiesController); + bot.use(fileIdController); } export async function startBot(database: Database) { - const localesPath = resolvePath(import.meta.url, '../locales'); + const localesPath = resolvePath(import.meta.url, "../locales"); const i18n = initLocaleEngine(localesPath); const bot = new TelegramBot(process.env.TELEGRAM_TOKEN); extendContext(bot, database); diff --git a/src/config/database.ts b/src/config/database.ts index b5debbb..7d67a31 100644 --- a/src/config/database.ts +++ b/src/config/database.ts @@ -1,12 +1,13 @@ -import { MongoClient } from 'mongodb'; -import type { Chat, Database, User } from '../types/database.js'; +import { MongoClient } from "mongodb"; +import type { Chat, Database, Property, User } from "../types/database.js"; export async function connectToDb() { const client = new MongoClient(process.env.DB_CONNECTION_STRING); await client.connect(); const mongoDb = client.db(); - const user = mongoDb.collection('user'); - const chat = mongoDb.collection('chat'); - const database: Database = { user, chat }; + const user = mongoDb.collection("user"); + const chat = mongoDb.collection("chat"); + const property = mongoDb.collection("properties"); + const database: Database = { user, chat, property }; return database; } diff --git a/src/config/locale-engine.ts b/src/config/locale-engine.ts index 19cfff1..64d094e 100644 --- a/src/config/locale-engine.ts +++ b/src/config/locale-engine.ts @@ -1,6 +1,6 @@ -import { I18n } from '@grammyjs/i18n'; +import { I18n } from "@grammyjs/i18n"; -export function initLocaleEngine(path: string, defaultLanguage = 'en') { +export function initLocaleEngine(path: string, defaultLanguage = "en") { const i18n = new I18n({ directory: path, defaultLanguage: defaultLanguage, diff --git a/src/controllers/fileId.ts b/src/controllers/fileId.ts new file mode 100644 index 0000000..b521c33 --- /dev/null +++ b/src/controllers/fileId.ts @@ -0,0 +1,11 @@ +import { Composer } from "grammy"; +import type { CustomContext } from "../types/context.js"; + +export const fileIdController = new Composer(); +fileIdController.on("message", async ctx => { + const fileId = ctx.update.message?.video?.file_id; + if (!fileId) { + return; + } + await ctx.reply(`File ID: ${fileId}`); +}); diff --git a/src/controllers/properties.ts b/src/controllers/properties.ts new file mode 100644 index 0000000..6782927 --- /dev/null +++ b/src/controllers/properties.ts @@ -0,0 +1,28 @@ +import { Composer } from "grammy"; +import type { CustomContext } from "../types/context.js"; +import type { Property } from "../types/database.js"; +import { + generatePropertyDescription, + generatePropertyPhotoAlbum +} from "../services/Property/property.service.js"; + +export const propertiesController = new Composer(); + +propertiesController.command("properties", async ctx => { + const properties = (await ctx.db.property.find({}).toArray()) as Property[]; + const currentPropertyIndex = 0; + + const currentProperty = properties[currentPropertyIndex]; + const totalProperties = properties.length; + + const { videoFileId: videoUrl, albumUrls } = currentProperty; + + const propertyDescription = generatePropertyDescription(currentProperty); + const propertyPhotoAlbum = generatePropertyPhotoAlbum(albumUrls); + await ctx.reply(`Property ${currentPropertyIndex + 1}/${totalProperties}`); + await ctx.reply(propertyDescription, { + parse_mode: "MarkdownV2" + }); + await ctx.replyWithVideo(videoUrl); + await ctx.replyWithMediaGroup(propertyPhotoAlbum); +}); diff --git a/src/controllers/start.ts b/src/controllers/start.ts index 869855a..3a3c7bc 100644 --- a/src/controllers/start.ts +++ b/src/controllers/start.ts @@ -1,10 +1,10 @@ -import { Composer } from 'grammy'; -import type { CustomContext } from '../types/context.js'; +import { Composer } from "grammy"; +import type { CustomContext } from "../types/context.js"; export const startController = new Composer(); -startController.command('start', async ctx => { - await ctx.text('start', { - name: ctx.entities.user.name, - chatName: ctx.entities.chat?.title ?? 'PM' +startController.command("start", async ctx => { + await ctx.text("start", { + name: ctx.config.user.name, + chatName: ctx.config.chat?.title ?? "PM" }); }); diff --git a/src/controllers/stop.ts b/src/controllers/stop.ts index 6482cfd..8616374 100644 --- a/src/controllers/stop.ts +++ b/src/controllers/stop.ts @@ -1,10 +1,10 @@ -import { Composer } from 'grammy'; -import type { CustomContext } from '../types/context.js'; +import { Composer } from "grammy"; +import type { CustomContext } from "../types/context.js"; export const stopController = new Composer(); -stopController.command('stop', async ctx => { - await ctx.text('stop', { - name: ctx.entities.user.name, - chatName: ctx.entities.chat?.title ?? 'PM' +stopController.command("stop", async ctx => { + await ctx.text("stop", { + name: ctx.config.user.name, + chatName: ctx.config.chat?.title ?? "PM" }); }); diff --git a/src/helpers/load-env.ts b/src/helpers/load-env.ts index 99b322e..7c21896 100644 --- a/src/helpers/load-env.ts +++ b/src/helpers/load-env.ts @@ -1,7 +1,7 @@ -import dotenv from 'dotenv'; -import { resolvePath } from './resolve-path.js'; +import dotenv from "dotenv"; +import { resolvePath } from "./resolve-path.js"; -export function loadEnv(configPath = '../../.env') { +export function loadEnv(configPath = "../../.env") { const fullPath = resolvePath(import.meta.url, configPath); dotenv.config({ path: fullPath diff --git a/src/helpers/resolve-path.ts b/src/helpers/resolve-path.ts index 22a115d..e736775 100644 --- a/src/helpers/resolve-path.ts +++ b/src/helpers/resolve-path.ts @@ -1,5 +1,5 @@ -import pathModule from 'path'; -import { fileURLToPath } from 'url'; +import pathModule from "path"; +import { fileURLToPath } from "url"; export function resolvePath(localPath: string, targetPath: string) { const __filename = fileURLToPath(localPath); diff --git a/src/index.ts b/src/index.ts index 745297d..10d1afa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ -import { startApp } from './config/app.js'; -import debugCreator from 'debug'; +import { startApp } from "./config/app.js"; +import debugCreator from "debug"; -const debug = debugCreator('index:'); +const debug = debugCreator("index:"); -debug('Starting App'); +debug("Starting App"); startApp(); -debug('App started'); +debug("App started"); diff --git a/src/services/Property/property.service.ts b/src/services/Property/property.service.ts new file mode 100644 index 0000000..4adccf2 --- /dev/null +++ b/src/services/Property/property.service.ts @@ -0,0 +1,36 @@ +import { type InputMediaPhoto } from "grammy/types"; +import { InputMediaBuilder } from "grammy"; +import type { Property } from "../../types/database.js"; + +export const generatePropertyDescription = (property: Property): string => { + const { + name, + collectionName, + availability, + price, + plotMetersSquared, + builtMetersSquared + } = property; + + // Property description uses Telegram's Markdown V2 + const propertyDescription = `*🏠 ${collectionName}: ${name}*\n\nPlot Size: ${plotMetersSquared}m2 \nBuilt Meters: ${builtMetersSquared}m2\nPrice: ${price + .toString() + .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}€${ + availability ? "" : "Reserved" + }\n`; + + return propertyDescription; +}; + +export const generatePropertyPhotoAlbum = ( + albumUrls: string[] +): InputMediaPhoto[] => { + const photoAlbum: InputMediaPhoto[] = []; + + for (const photoUrl of albumUrls) { + const photo = InputMediaBuilder.photo(photoUrl); + photoAlbum.push(photo); + } + + return photoAlbum; +}; diff --git a/src/services/chat.ts b/src/services/chat.ts index bc186a1..2a9b2c7 100644 --- a/src/services/chat.ts +++ b/src/services/chat.ts @@ -1,4 +1,4 @@ -import type { Chat, Database } from '../types/database.js'; +import type { Chat, Database } from "../types/database.js"; async function createChat(args: { db: Database; @@ -23,7 +23,7 @@ export async function getOrCreateChat(args: { const chat = await args.db.chat.findOneAndUpdate( { chatId: args.chatId }, { $set: { title: args.title } }, - { returnDocument: 'after' } + { returnDocument: "after" } ); if (chat.ok && chat.value) { diff --git a/src/services/context.ts b/src/services/context.ts index ef1c634..6966090 100644 --- a/src/services/context.ts +++ b/src/services/context.ts @@ -1,11 +1,10 @@ -import type { CustomContext, CustomContextMethods } from '../types/context.js'; +import type { CustomContext, CustomContextMethods } from "../types/context.js"; export function createReplyWithTextFunc( ctx: CustomContext -): CustomContextMethods['text'] { +): CustomContextMethods["text"] { return (resourceKey, templateData, extra = {}) => { - extra.parse_mode = 'HTML'; - extra.disable_web_page_preview = true; + extra.parse_mode = "HTML"; const text = ctx.i18n.t(resourceKey, templateData); return ctx.reply(text, extra); }; diff --git a/src/services/user.ts b/src/services/user.ts index f557af0..bdfb993 100644 --- a/src/services/user.ts +++ b/src/services/user.ts @@ -1,4 +1,4 @@ -import type { Database, User } from '../types/database.js'; +import type { Database, User } from "../types/database.js"; export function buildName(firstName: string, lastName?: string) { return lastName ? `${firstName} ${lastName}` : firstName; @@ -27,7 +27,7 @@ export async function getOrCreatePlayer(args: { const user = await args.db.user.findOneAndUpdate( { userId: args.userId }, { $set: { name: args.name } }, - { returnDocument: 'after' } + { returnDocument: "after" } ); if (user.ok && user.value) { diff --git a/src/types/context.ts b/src/types/context.ts index 09ef0c8..012a21e 100644 --- a/src/types/context.ts +++ b/src/types/context.ts @@ -1,16 +1,16 @@ -import type { Context, SessionFlavor } from 'grammy'; -import type { I18nContextFlavor, TemplateData } from '@grammyjs/i18n'; -import type { Extra } from './telegram.js'; -import type { Chat, Database, User } from './database.js'; +import type { Context, SessionFlavor } from "grammy"; +import type { I18nContextFlavor, TemplateData } from "@grammyjs/i18n"; +import type { Extra } from "./telegram.js"; +import type { Chat, Database, User } from "./database.js"; export interface Custom { text: ( text: string, templateData?: TemplateData, extra?: Extra - ) => ReturnType; + ) => ReturnType; - entities: { + config: { user: User; chat: Chat | null; }; diff --git a/src/types/database.ts b/src/types/database.ts index dda9b7e..70096a0 100644 --- a/src/types/database.ts +++ b/src/types/database.ts @@ -1,10 +1,26 @@ -import type { Collection } from 'mongodb'; +import type { Collection } from "mongodb"; export interface User { userId: number; name: string; } +export interface Property { + _id: string; + collectionName: "SaliSol Hills" | "SaliSol Resort" | "SaliSol Golf"; + name: string; + price: number; + availability: boolean; + videoFileId: string; + thumbnailUrl: string; + albumUrls: string[]; + builtMetersSquared: number; + plotMetersSquared: number; + dropboxUrl: string; + telegramContactUrl: string; + websiteUrl: string; +} + export interface Chat { chatId: number; title: string; @@ -13,4 +29,5 @@ export interface Chat { export interface Database { user: Collection; chat: Collection; + property: Collection; } diff --git a/src/types/telegram.ts b/src/types/telegram.ts index bbf91ef..d889b25 100644 --- a/src/types/telegram.ts +++ b/src/types/telegram.ts @@ -1,8 +1,8 @@ -import type { Api, Bot as TelegramBot, NextFunction } from 'grammy'; -import type { CustomContext } from './context.js'; +import type { Api, Bot as TelegramBot, NextFunction } from "grammy"; +import type { CustomContext } from "./context.js"; export type Bot = TelegramBot; export type Handler = (ctx: CustomContext, next?: NextFunction) => void; -export type Extra = Parameters[2]; +export type Extra = Parameters[2];