From 42e7927891bc0ea90b38953b6651dec16226dfc1 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhytnyk Date: Mon, 26 Feb 2024 23:29:22 +0200 Subject: [PATCH] Start import media history by command --- package-lock.json | 92 ++++++++-------- package.json | 6 +- src/bot/commands/mediaTracker.command.ts | 133 ++++++++++++++--------- 3 files changed, 132 insertions(+), 99 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b1580d..fdbfaef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ai_bot", - "version": "1.2.0", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ai_bot", - "version": "1.2.0", + "version": "1.2.1", "dependencies": { "@iamtraction/google-translate": "^2.0.1", "@xenova/transformers": "^2.15.1", @@ -23,8 +23,8 @@ "devDependencies": { "@types/fluent-ffmpeg": "^2.1.24", "@types/node": "^20.11.20", - "@typescript-eslint/eslint-plugin": "^7.0.2", - "@typescript-eslint/parser": "^7.0.2", + "@typescript-eslint/eslint-plugin": "^7.1.0", + "@typescript-eslint/parser": "^7.1.0", "eslint": "^8.57.0", "nodemon": "^3.1.0", "prettier": "^3.2.5", @@ -415,16 +415,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.2.tgz", - "integrity": "sha512-/XtVZJtbaphtdrWjr+CJclaCVGPtOdBpFEnvtNf/jRV0IiEemRrL0qABex/nEt8isYcnFacm3nPHYQwL+Wb7qg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz", + "integrity": "sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.0.2", - "@typescript-eslint/type-utils": "7.0.2", - "@typescript-eslint/utils": "7.0.2", - "@typescript-eslint/visitor-keys": "7.0.2", + "@typescript-eslint/scope-manager": "7.1.0", + "@typescript-eslint/type-utils": "7.1.0", + "@typescript-eslint/utils": "7.1.0", + "@typescript-eslint/visitor-keys": "7.1.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -450,15 +450,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.2.tgz", - "integrity": "sha512-GdwfDglCxSmU+QTS9vhz2Sop46ebNCXpPPvsByK7hu0rFGRHL+AusKQJ7SoN+LbLh6APFpQwHKmDSwN35Z700Q==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.0.tgz", + "integrity": "sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.0.2", - "@typescript-eslint/types": "7.0.2", - "@typescript-eslint/typescript-estree": "7.0.2", - "@typescript-eslint/visitor-keys": "7.0.2", + "@typescript-eslint/scope-manager": "7.1.0", + "@typescript-eslint/types": "7.1.0", + "@typescript-eslint/typescript-estree": "7.1.0", + "@typescript-eslint/visitor-keys": "7.1.0", "debug": "^4.3.4" }, "engines": { @@ -478,13 +478,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.2.tgz", - "integrity": "sha512-l6sa2jF3h+qgN2qUMjVR3uCNGjWw4ahGfzIYsCtFrQJCjhbrDPdiihYT8FnnqFwsWX+20hK592yX9I2rxKTP4g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", + "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.0.2", - "@typescript-eslint/visitor-keys": "7.0.2" + "@typescript-eslint/types": "7.1.0", + "@typescript-eslint/visitor-keys": "7.1.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -495,13 +495,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.2.tgz", - "integrity": "sha512-IKKDcFsKAYlk8Rs4wiFfEwJTQlHcdn8CLwLaxwd6zb8HNiMcQIFX9sWax2k4Cjj7l7mGS5N1zl7RCHOVwHq2VQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.0.tgz", + "integrity": "sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.0.2", - "@typescript-eslint/utils": "7.0.2", + "@typescript-eslint/typescript-estree": "7.1.0", + "@typescript-eslint/utils": "7.1.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -522,9 +522,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.2.tgz", - "integrity": "sha512-ZzcCQHj4JaXFjdOql6adYV4B/oFOFjPOC9XYwCaZFRvqN8Llfvv4gSxrkQkd2u4Ci62i2c6W6gkDwQJDaRc4nA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", + "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -535,13 +535,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.2.tgz", - "integrity": "sha512-3AMc8khTcELFWcKcPc0xiLviEvvfzATpdPj/DXuOGIdQIIFybf4DMT1vKRbuAEOFMwhWt7NFLXRkbjsvKZQyvw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", + "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.0.2", - "@typescript-eslint/visitor-keys": "7.0.2", + "@typescript-eslint/types": "7.1.0", + "@typescript-eslint/visitor-keys": "7.1.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -563,17 +563,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.2.tgz", - "integrity": "sha512-PZPIONBIB/X684bhT1XlrkjNZJIEevwkKDsdwfiu1WeqBxYEEdIgVDgm8/bbKHVu+6YOpeRqcfImTdImx/4Bsw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.0.tgz", + "integrity": "sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.0.2", - "@typescript-eslint/types": "7.0.2", - "@typescript-eslint/typescript-estree": "7.0.2", + "@typescript-eslint/scope-manager": "7.1.0", + "@typescript-eslint/types": "7.1.0", + "@typescript-eslint/typescript-estree": "7.1.0", "semver": "^7.5.4" }, "engines": { @@ -588,12 +588,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.2.tgz", - "integrity": "sha512-8Y+YiBmqPighbm5xA2k4wKTxRzx9EkBu7Rlw+WHqMvRJ3RPz/BMBO9b2ru0LUNmXg120PHUXD5+SWFy2R8DqlQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", + "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.0.2", + "@typescript-eslint/types": "7.1.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { diff --git a/package.json b/package.json index f83b932..df9f4c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ai_bot", - "version": "1.2.0", + "version": "1.2.1", "private": true, "main": "src/app.ts", "type": "module", @@ -30,8 +30,8 @@ "devDependencies": { "@types/fluent-ffmpeg": "^2.1.24", "@types/node": "^20.11.20", - "@typescript-eslint/eslint-plugin": "^7.0.2", - "@typescript-eslint/parser": "^7.0.2", + "@typescript-eslint/eslint-plugin": "^7.1.0", + "@typescript-eslint/parser": "^7.1.0", "eslint": "^8.57.0", "nodemon": "^3.1.0", "prettier": "^3.2.5", diff --git a/src/bot/commands/mediaTracker.command.ts b/src/bot/commands/mediaTracker.command.ts index 62d953e..f4cc607 100644 --- a/src/bot/commands/mediaTracker.command.ts +++ b/src/bot/commands/mediaTracker.command.ts @@ -16,6 +16,7 @@ export class MediaTrackerCommand extends Command { private fileService = FileService.getInstance(); private tgClient: TelegramClient | null = null; private similarFoundVariants = ['ось тут', 'ще тут', 'і ось', 'навіть це', 'і оце щось схоже']; + private isMediaImporting = false; handle(): void { this.bot.on(message('photo'), async (ctx) => { @@ -35,38 +36,23 @@ export class MediaTrackerCommand extends Command { reply_parameters: { message_id: ctx.message.message_id }, }); }); + this.bot.command('starthistoryimport', async (ctx) => { + this.startHistoryImport(ctx); + }); } private async messageHandler(ctx: NarrowedContext>, fileId: string) { + if (this.isMediaImporting) return; const chatId = ctx.chat.id; const messageId = ctx.message.message_id; - const chatStateRepository = this.dataSource.getRepository(ChatState); - const chatPhotoMessageRepository = this.dataSource.getRepository(ChatPhotoMessage); - try { - // Check media import is done - const chatState = await chatStateRepository.findOneBy({ chatId: String(chatId) }); - const isMediaImported = chatState?.isMediaImported ?? false; - if (!isMediaImported) { - const [latestChatPhotoMessage] = await chatPhotoMessageRepository.find({ - where: { chatId: String(chatId) }, - order: { messageId: 'DESC' }, - take: 1, - }); - const lastImportedMessageId = latestChatPhotoMessage ? Number(latestChatPhotoMessage.messageId) : 0; - const lastMessageId = messageId; - await this.importChatMessages(chatId, lastImportedMessageId, lastMessageId); - // Save to DB - const chatState = new ChatState(); - chatState.chatId = String(chatId); - (chatState.isMediaImported = true), await chatStateRepository.save(chatState); - } // DB similarity search const imageEmbeddingString = await this.getEmbeddingStringByImageFileId(fileId); type Messages = { messageId: string; similarity: number; }; + const chatPhotoMessageRepository = this.dataSource.getRepository(ChatPhotoMessage); const messages = await chatPhotoMessageRepository .createQueryBuilder('msg') .select('msg.messageId', 'messageId') @@ -107,6 +93,55 @@ export class MediaTrackerCommand extends Command { } } + private async startHistoryImport(ctx: NarrowedContext>) { + const messageId = ctx.message.message_id; + if (this.isMediaImporting) { + await ctx.reply('😡 Я тут працюю, тужуся, а ти відволікаєш', { + reply_parameters: { message_id: messageId }, + }); + return; + } + this.isMediaImporting = true; + try { + const chatId = ctx.chat.id; + const chatStateRepository = this.dataSource.getRepository(ChatState); + const chatState = await chatStateRepository.findOneBy({ chatId: String(chatId) }); + const isMediaImported = chatState?.isMediaImported ?? false; + if (isMediaImported) { + await ctx.reply('🍧 Нема потреби. Усе же зроблено.', { + reply_parameters: { message_id: messageId }, + }); + } else { + await ctx.reply('🏃 Взяв у роботу!', { + reply_parameters: { message_id: messageId }, + }); + + const chatPhotoMessageRepository = this.dataSource.getRepository(ChatPhotoMessage); + const [latestChatPhotoMessage] = await chatPhotoMessageRepository.find({ + where: { chatId: String(chatId) }, + order: { messageId: 'DESC' }, + take: 1, + }); + const lastImportedMessageId = latestChatPhotoMessage ? Number(latestChatPhotoMessage.messageId) : 0; + await this.importChatMessages(chatId, lastImportedMessageId, messageId); + // Save to DB + const chatState = new ChatState(); + chatState.chatId = String(chatId); + (chatState.isMediaImported = true), await chatStateRepository.save(chatState); + + await ctx.reply('😮‍💨 Фух... Усе підтягнув!', { + reply_parameters: { message_id: messageId }, + }); + } + } catch (e) { + await ctx.reply('📛 Халепа', { + reply_parameters: { message_id: messageId }, + }); + } finally { + this.isMediaImporting = false; + } + } + private async getEmbeddingStringByImageFileId(fileId: string) { const fileUrl = await this.bot.telegram.getFileLink(fileId); const imageBuffer = await this.fileService.getBufferByUrl(fileUrl); @@ -132,36 +167,34 @@ export class MediaTrackerCommand extends Command { reverse: true, filter: new Api.InputMessagesFilterPhotoVideo(), })) { - if (message.id < lastMessageId) { - const t1 = performance.now(); - let fleLocation: Api.InputPhotoFileLocation | Api.InputDocumentFileLocation | null = null; - if (message.video) { - const { id, fileReference, accessHash } = message.video; - const thumbSize = message.video.thumbs?.findLast(({ className }) => className === 'PhotoSize')?.type ?? 'm'; - fleLocation = new Api.InputDocumentFileLocation({ id, fileReference, accessHash, thumbSize }); - } else if (message.photo) { - const photo = message.photo as Api.Photo; - const { id, fileReference, accessHash } = photo; - const thumbSize = photo.sizes.at(-1)?.type ?? 'm'; - fleLocation = new Api.InputPhotoFileLocation({ id, fileReference, accessHash, thumbSize }); - } - if (fleLocation) { - const imageBuffer = await this.tgClient.downloadFile(fleLocation); - if (imageBuffer instanceof Buffer) { - // Get image embedding - const rawImage = await this.aiService.getRawImageFromBuffer(imageBuffer); - const imageEmbedding = await this.aiService.getImageClipEmbedding(rawImage); - const imageEmbeddingString = JSON.stringify(imageEmbedding); - // Save to DB - const chatPhotoMessage = new ChatPhotoMessage(); - chatPhotoMessage.chatId = String(chatId); - chatPhotoMessage.messageId = String(message.id); - chatPhotoMessage.embedding = imageEmbeddingString; - await this.dataSource.manager.save(chatPhotoMessage); - // Logging - const t2 = performance.now(); - console.log(`Imported message ${message.id}/${lastMessageId - 1} (${Math.round(t2 - t1)} ms)`); - } + const t1 = performance.now(); + let fleLocation: Api.InputPhotoFileLocation | Api.InputDocumentFileLocation | null = null; + if (message.video) { + const { id, fileReference, accessHash } = message.video; + const thumbSize = message.video.thumbs?.findLast(({ className }) => className === 'PhotoSize')?.type ?? 'm'; + fleLocation = new Api.InputDocumentFileLocation({ id, fileReference, accessHash, thumbSize }); + } else if (message.photo) { + const photo = message.photo as Api.Photo; + const { id, fileReference, accessHash } = photo; + const thumbSize = photo.sizes.at(-1)?.type ?? 'm'; + fleLocation = new Api.InputPhotoFileLocation({ id, fileReference, accessHash, thumbSize }); + } + if (fleLocation) { + const imageBuffer = await this.tgClient.downloadFile(fleLocation); + if (imageBuffer instanceof Buffer) { + // Get image embedding + const rawImage = await this.aiService.getRawImageFromBuffer(imageBuffer); + const imageEmbedding = await this.aiService.getImageClipEmbedding(rawImage); + const imageEmbeddingString = JSON.stringify(imageEmbedding); + // Save to DB + const chatPhotoMessage = new ChatPhotoMessage(); + chatPhotoMessage.chatId = String(chatId); + chatPhotoMessage.messageId = String(message.id); + chatPhotoMessage.embedding = imageEmbeddingString; + await this.dataSource.manager.save(chatPhotoMessage); + // Logging + const t2 = performance.now(); + console.log(`Imported message ${message.id}/${lastMessageId} (${Math.round(t2 - t1)} ms)`); } } }