bump libs

migrate to typescript 6
remove ytdl feature
This commit is contained in:
2026-05-01 07:05:17 +03:00
parent ac51702f00
commit 13b41c3026
56 changed files with 1069 additions and 1857 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"env": {
"browser": true,
"browser": false,
"es2021": true
},
"extends": [
+15 -16
View File
@@ -5,7 +5,7 @@
"": {
"name": "tg-chat-bot",
"dependencies": {
"@google/genai": "^1.50.1",
"@google/genai": "^1.51.0",
"@libsql/client": "^0.17.3",
"@mistralai/mistralai": "^2.2.1",
"@napi-rs/canvas": "^0.1.100",
@@ -24,8 +24,7 @@
"systeminformation": "^5.31.5",
"twemoji": "^14.0.2",
"typescript-telegram-bot-api": "^0.14.0",
"youtubei.js": "^17.0.1",
"zod": "^4.3.6",
"zod": "^4.4.1",
},
"devDependencies": {
"@types/bun": "^1.3.13",
@@ -34,10 +33,10 @@
"@types/qrcode": "^1.5.6",
"@typescript-eslint/eslint-plugin": "^8.59.1",
"@typescript-eslint/parser": "^8.59.1",
"drizzle-kit": "^1.0.0-beta.22",
"drizzle-kit": "^1.0.0-rc.1",
"eslint": "^10.2.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"typescript": "^6.0.3",
},
},
},
@@ -46,8 +45,6 @@
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
"@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
@@ -118,7 +115,7 @@
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="],
"@google/genai": ["@google/genai@1.50.1", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-YbkX7H9+1Pt8wOt7DDREy8XSoiL6fRDzZQRyaVBarFf8MR3zHGqVdvM4cLbDXqPhxqvegZShgfxb8kw9C7YhAQ=="],
"@google/genai": ["@google/genai@1.51.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-vTZZF3CSimN7cn2zsLpW2p5WF0eZa5Gz69ITMPCNHpPrDlAstOfGifSfi0p/s9Z9400f7xJRkgvkQNrcM7pJ6w=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
@@ -406,7 +403,7 @@
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
"drizzle-kit": ["drizzle-kit@1.0.0-beta.9-e89174b", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "tsx": "^4.20.6" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-Xrw3k8E2CbSZr+kqe3k5W4oxd2fbEyczjKtyGIkAq0x9Wqpa/VtAT6Mkh83sIzqG4OSN7lOoUafsDxSE/AR7RA=="],
"drizzle-kit": ["drizzle-kit@1.0.0-rc.1", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "get-tsconfig": "^4.13.6", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-eDvXzRhke7OwvmN7AciGOU1E2y17MKNhghGciyw1RbmmkuD/2KDXLn3rFRZcDBmfj6CQSEnyvbU+7Fqrn2JQyA=="],
"drizzle-orm": ["drizzle-orm@1.0.0-beta.22", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@sinclair/typebox": ">=0.34.8", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "arktype": ">=2.0.0", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5", "typebox": ">=1.0.0", "valibot": ">=1.0.0-beta.7", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@sinclair/typebox", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/mssql", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "arktype", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mssql", "mysql2", "pg", "postgres", "sql.js", "sqlite3", "typebox", "valibot", "zod"] }, "sha512-F+DZyVIvH0oVKa/w08Cle1xfoH+pc+htIXHG/frnMLG72aby9NYYr9oc+9XvghnoO4umxFItduz0OMmQJMnenw=="],
@@ -520,7 +517,7 @@
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
"get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
"get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="],
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
@@ -580,6 +577,8 @@
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
@@ -626,8 +625,6 @@
"merge-deep": ["merge-deep@3.0.3", "", { "dependencies": { "arr-union": "^3.1.0", "clone-deep": "^0.2.4", "kind-of": "^3.0.2" } }, "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA=="],
"meriyah": ["meriyah@6.1.4", "", {}, "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ=="],
"mime-db": ["mime-db@1.46.0", "", {}, "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ=="],
"mime-types": ["mime-types@2.1.29", "", { "dependencies": { "mime-db": "1.46.0" } }, "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ=="],
@@ -794,7 +791,7 @@
"typed-query-selector": ["typed-query-selector@2.12.1", "", {}, "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
"typescript-telegram-bot-api": ["typescript-telegram-bot-api@0.14.0", "", { "dependencies": { "axios": "^1.7.7", "form-data": "^4.0.1" } }, "sha512-PjigN50TxenH5LjPLqiZopyNizy3tObh3bdLmKwPBo+rW7zUt7iNzJKcNnX7FkMZUmJ2DBvnZ+W+L4oQNwU7eQ=="],
@@ -834,9 +831,7 @@
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"youtubei.js": ["youtubei.js@17.0.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.0.0", "meriyah": "^6.1.4" } }, "sha512-1lO4b8UqMDzE0oh2qEGzbBOd4UYRdxn/4PdpRM7BGTHxM6ddsEsKZTu90jp8V9FHVgC2h1UirQyqoqLiKwl+Zg=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"zod": ["zod@4.4.1", "", {}, "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q=="],
"zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
@@ -850,6 +845,8 @@
"@libsql/isomorphic-ws/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"@mistralai/mistralai/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@puppeteer/browsers/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"@puppeteer/browsers/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
@@ -902,6 +899,8 @@
"tsx/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
"tsx/get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
"yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
+677 -1162
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -8,7 +8,7 @@
"bun:start": "bun run dist/index.js"
},
"dependencies": {
"@google/genai": "^1.50.1",
"@google/genai": "^1.51.0",
"@libsql/client": "^0.17.3",
"@mistralai/mistralai": "^2.2.1",
"@napi-rs/canvas": "^0.1.100",
@@ -27,8 +27,7 @@
"systeminformation": "^5.31.5",
"twemoji": "^14.0.2",
"typescript-telegram-bot-api": "^0.14.0",
"youtubei.js": "^17.0.1",
"zod": "^4.3.6"
"zod": "^4.4.1"
},
"devDependencies": {
"@types/bun": "^1.3.13",
@@ -37,9 +36,9 @@
"@types/fluent-ffmpeg": "^2.1.28",
"@typescript-eslint/eslint-plugin": "^8.59.1",
"@typescript-eslint/parser": "^8.59.1",
"drizzle-kit": "^1.0.0-beta.22",
"drizzle-kit": "^1.0.0-rc.1",
"eslint": "^10.2.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
"typescript": "^6.0.3"
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ export abstract class CallbackCommand {
abstract text: string;
abstract data: string;
requirements?: Requirements = null;
requirements?: Requirements | null = null;
abstract execute(query: CallbackQuery): Promise<void>;
+2 -2
View File
@@ -9,7 +9,7 @@ export abstract class Command {
command?: string | string[];
argsMode: ArgsMode = "none";
requirements?: Requirements = null;
requirements?: Requirements | null = null;
title?: string;
description?: string;
@@ -24,7 +24,7 @@ export abstract class Command {
abstract execute(
msg: Message,
match?: RegExpExecArray
match?: RegExpExecArray | null
): Promise<void>;
}
+1 -1
View File
@@ -4,7 +4,7 @@ export class Requirements {
requirements: Requirement[] = [];
private constructor(requirements?: Requirement[]) {
this.requirements = requirements;
this.requirements = requirements || [];
}
static Build(...requirements: Requirement[]): Requirements {
+2 -2
View File
@@ -3,7 +3,7 @@ import {CallbackCommand} from "../base/callback-command";
export class Cancel extends CallbackCommand {
text = "❌ Отменить";
data = null;
data = "";
constructor(text?: string, data?: string) {
super();
@@ -13,7 +13,7 @@ export class Cancel extends CallbackCommand {
}
static withData(data?: string): Cancel {
return new Cancel(null, data);
return new Cancel("", data);
}
async execute(): Promise<void> {
@@ -1,36 +0,0 @@
import {CallbackCommand} from "../base/callback-command";
import {CallbackQuery} from "typescript-telegram-bot-api";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {commands} from "../index";
import {YouTubeDownload} from "../commands/youtube-download";
const downloadText = " 📥 Скачать";
const getFromCacheText = "📥 Загрузить из кэша";
export class DownloadYtVideo extends CallbackCommand {
data = "/ytdl";
text = " 📥 Скачать";
requirements = Requirements.Build(Requirement.SAME_USER);
constructor(text?: string, data?: string) {
super();
this.text = text || this.text;
this.data = data || this.data;
}
static withData(inCache?: boolean, data?: string): DownloadYtVideo {
return new DownloadYtVideo(inCache ? getFromCacheText : downloadText, data);
}
async execute(query: CallbackQuery): Promise<void> {
const videoId = query.data.split(" ")[1];
if (!videoId) return;
const yt = commands.find(c => c instanceof YouTubeDownload);
if (!yt) return;
await yt.downloadYouTubeVideo(query.message, {videoId: videoId});
}
}
+3 -1
View File
@@ -16,6 +16,8 @@ export class OllamaCancel extends CallbackCommand {
requirements = Requirements.Build(Requirement.SAME_USER);
async execute(query: CallbackQuery): Promise<void> {
if (!query.message || !query.data) return;
const chatId = query.message.chat.id;
const fromId = query.from.id;
const messageId = query.message.message_id;
@@ -44,7 +46,7 @@ export class OllamaCancel extends CallbackCommand {
let content: string | null = null;
if (msg?.text?.trim()?.length > 0) {
if (msg?.text?.trim()?.length) {
content = msg?.text.trim();
if (content.length + Environment.ollamaCancelledText.length > 4096) {
content = content.substring(0, 4096 - Environment.ollamaCancelledText.length - 2) + "\n";
+1 -1
View File
@@ -12,7 +12,7 @@ export class TryAgain extends CallbackCommand {
}
static withData(data?: string): TryAgain {
return new TryAgain(null, data);
return new TryAgain("", data);
}
async execute(): Promise<void> {
-15
View File
@@ -1,15 +0,0 @@
import {CallbackCommand} from "../base/callback-command";
import {CallbackQuery} from "typescript-telegram-bot-api";
import {processYouTubeLink} from "../util/utils";
export class YtInfo extends CallbackCommand {
data = "/ytinfo";
text: string;
async execute(query: CallbackQuery): Promise<void> {
const videoId = query.data.split(" ")[1];
if (!videoId) return;
await processYouTubeLink(query.message, null, videoId);
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ export class AdminsAdd extends Command {
);
async execute(msg: Message): Promise<void> {
if (!msg.reply_to_message) return;
if (!msg.reply_to_message || !msg.reply_to_message.from) return;
const id = msg.reply_to_message.from.id;
const text = fullName(msg.reply_to_message.from);
+1 -1
View File
@@ -18,7 +18,7 @@ export class AdminsRemove extends Command {
);
async execute(msg: Message): Promise<void> {
if (!msg.reply_to_message) return;
if (!msg.reply_to_message || !msg.reply_to_message.from) return;
const id = msg.reply_to_message.from.id;
const text = fullName(msg.reply_to_message.from);
+2 -2
View File
@@ -13,7 +13,7 @@ export class Ae extends Command {
requirements = Requirements.Build(Requirement.BOT_CREATOR);
async execute(msg: Message, params?: RegExpExecArray) {
const match = params?.[3];
const match = params?.[3] || "";
try {
let e = eval(match);
@@ -21,7 +21,7 @@ export class Ae extends Command {
e = ((typeof e == "string") ? e : JSON.stringify(e));
await oldSendMessage(msg, e).catch(async () => await errorPlaceholder(msg));
} catch (e) {
} catch (e: any) {
const text = e.message.toString();
if (text.includes("is not defined")) {
+1 -1
View File
@@ -19,7 +19,7 @@ export class Ban extends Command {
);
async execute(msg: Message) {
if (!msg.reply_to_message) return;
if (!msg.reply_to_message || !msg.from || ! msg.reply_to_message.from) return;
const user = msg.reply_to_message.from;
const userId = user.id;
+1 -1
View File
@@ -12,7 +12,7 @@ export class Choice extends Command {
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("match", match);
const payload = match[3];
const payload = match?.[3] || "";
const re =
/\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^,]+?))\s*(?:,|$)/g;
+2 -2
View File
@@ -11,8 +11,8 @@ export class Dice extends Command {
description = "Sends random or specific dice";
async execute(msg: Message): Promise<void> {
const split = msg.text.split("/dice ");
const secondPart = split[1]?.trim();
const split = msg.text?.split("/dice ");
const secondPart = split?.[1]?.trim() || "";
const emojiIndex = emojis.indexOf(secondPart);
const emojiToDice: DiceEmoji = (emojiIndex >= 0 ? emojis[emojiIndex] : randomValue(emojis)) as DiceEmoji;
+2 -2
View File
@@ -47,14 +47,14 @@ export class Distort extends Command {
const inputBuf = await downloadTelegramFile(file.file_path);
const outBuf = await waveDistortSharp(inputBuf, amp, wavelength);
const outBuf = await waveDistortSharp(<Buffer>inputBuf, amp, wavelength);
await bot.sendPhoto({
chat_id: chatId,
photo: outBuf,
caption: `Искажение готово ✅ (amp=${amp}, wavelength=${wavelength})`,
});
} catch (e) {
} catch (e: any) {
await oldReplyToMessage(
msg, `Не получилось исказить изображение: ${e?.message ?? String(e)}`
).catch(logError);
+16 -11
View File
@@ -4,7 +4,7 @@ import {bot, googleAi} from "../index";
import {MessageStore} from "../common/message-store";
import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {ApiError} from "@google/genai";
import {
collectReplyChainText,
escapeMarkdownV2Text,
@@ -14,6 +14,7 @@ import {
startIntervalEditor
} from "../util/utils";
import {ChatCommand} from "../base/chat-command";
import {ApiError} from "@google/genai";
export class GeminiChat extends ChatCommand {
command = "gemini";
@@ -26,11 +27,11 @@ export class GeminiChat extends ChatCommand {
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("match", match);
return this.executeGemini(msg, match?.[3]);
return this.executeGemini(msg, match?.[3] || "");
}
async executeGemini(msg: Message, text: string): Promise<void> {
if (!text || text.trim().length === 0) return;
if (!text || !text.trim().length) return;
const chatId = msg.chat.id;
@@ -77,7 +78,7 @@ export class GeminiChat extends ChatCommand {
});
}
let waitMessage: Message;
let waitMessage: Message | null = null;
const startTime = Date.now();
@@ -95,7 +96,7 @@ export class GeminiChat extends ChatCommand {
const stream = await googleAi.interactions.create({
model: Environment.GEMINI_MODEL,
input: input,
input: input as any,
stream: true
});
@@ -109,7 +110,7 @@ export class GeminiChat extends ChatCommand {
await bot.editMessageText(
{
chat_id: chatId,
message_id: waitMessage.message_id,
message_id: <number>waitMessage?.message_id,
text: escapeMarkdownV2Text(text),
parse_mode: "MarkdownV2"
}
@@ -117,9 +118,11 @@ export class GeminiChat extends ChatCommand {
console.log("editMessageText", text);
if (waitMessage) {
waitMessage.reply_to_message = msg;
waitMessage.text = text;
await MessageStore.put(waitMessage);
}
},
onStop: async () => {
}
@@ -175,17 +178,19 @@ export class GeminiChat extends ChatCommand {
await replyToMessage({message: waitMessage, text: `⏱️ ${diff}s`});
}
}
} catch (error) {
logError(error);
} catch (e: any) {
logError(e);
if (error instanceof ApiError) {
if (error.status === 429) {
if (waitMessage) {
if (e instanceof ApiError) {
if (e.status === 429) {
await oldReplyToMessage(waitMessage, "На сегодня всё, лимиты закончились.").catch(logError);
return;
}
}
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError);
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${e.toString()}`).catch(logError);
}
}
}
}
+6 -4
View File
@@ -18,14 +18,14 @@ export class GeminiGenerateImage extends Command {
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("match", match);
const prompt = match?.[3];
const prompt = match?.[3] || "";
return this.executeGenImage(msg, prompt);
}
async executeGenImage(msg: Message, text: string): Promise<void> {
if (!text || text.trim().length === 0) return;
if (!text || !text.trim().length) return;
let waitMessage: Message;
let waitMessage: Message | null = null;
try {
waitMessage = await replyToMessage({
@@ -47,9 +47,10 @@ export class GeminiGenerateImage extends Command {
console.log(`Output ${index + 1}: ${output}`);
}
});
} catch (e) {
} catch (e: any) {
logError(e);
if (waitMessage) {
await replyToMessage({
message: waitMessage,
text: `Произошла ошибка!\n${e.toString()}`,
@@ -58,3 +59,4 @@ export class GeminiGenerateImage extends Command {
}
}
}
}
+2 -2
View File
@@ -20,9 +20,9 @@ export class GeminiGetModel extends Command {
return {
vision: {supported: true},
ocr: null,
ocr: undefined,
thinking: {supported: info.thinking},
tools: null
tools: undefined
};
} catch (e) {
logError(e);
+1 -1
View File
@@ -17,7 +17,7 @@ export class GeminiListModels extends Command {
console.log(listResponse);
const modelsString = listResponse.page
.sort((a, b) => a.name.localeCompare(b.name))
.sort((a, b) => (a.name || "").localeCompare((b.name || "")))
.map(e => `${e.name}`)
.join("\n");
+1
View File
@@ -11,6 +11,7 @@ export class Help extends Command {
description = "Show list of commands";
async execute(msg: Message) {
if (!msg.from) return;
let text = "Commands:\n\n";
commands.forEach(c => {
+2 -2
View File
@@ -7,9 +7,9 @@ export class Id extends Command {
description = "ID of chat, user and reply (if replied to any message)";
async execute(msg: Message): Promise<void> {
let text = `chat id: \n\`\`\`${msg.chat.id}\`\`\` \nfrom id: \n\`\`\`${msg.from.id}\`\`\``;
let text = `chat id: \n\`\`\`${msg.chat.id}\`\`\` \nfrom id: \n\`\`\`${msg.from?.id}\`\`\``;
if (msg.reply_to_message) {
text += ` \nreply id: \n\`\`\`${msg.reply_to_message.from.id}\`\`\``;
text += ` \nreply id: \n\`\`\`${msg.reply_to_message.from?.id}\`\`\``;
}
await oldReplyToMessage(msg, text, "MarkdownV2").catch(logError);
+1 -1
View File
@@ -19,7 +19,7 @@ export class Ignore extends Command {
);
async execute(msg: Message) {
if (!msg.reply_to_message) return;
if (!msg.reply_to_message || !msg.reply_to_message.from) return;
const id = msg.reply_to_message.from.id;
const text = fullName(msg.reply_to_message.from);
+6 -6
View File
@@ -16,7 +16,7 @@ export class Info extends Command {
async execute(msg: Message): Promise<void> {
const aiProvider = Environment.DEFAULT_AI_PROVIDER;
const aiModel = getCurrentModel();
let aiModelCapabilities: AiModelCapabilities = {};
let aiModelCapabilities: AiModelCapabilities | null = {};
try {
aiModelCapabilities = await getCurrentModelCapabilities();
@@ -33,11 +33,11 @@ export class Info extends Command {
`provider: ${aiProvider.toLowerCase()}\n` +
`model: ${aiModel}\n\n` +
`vision${aiModelCapabilities.vision?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities.vision?.supported)}\n` +
`ocr${aiModelCapabilities.ocr?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities.ocr?.supported)}\n` +
`thinking${aiModelCapabilities.thinking?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities.thinking?.supported)}\n` +
`tools${aiModelCapabilities.tools?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities.tools?.supported)}\n` +
`audio${aiModelCapabilities.audio?.external ? "(ext)": ""}: ${boolToEmoji(aiModelCapabilities.audio?.supported)}` +
`vision${aiModelCapabilities?.vision?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.vision?.supported)}\n` +
`ocr${aiModelCapabilities?.ocr?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.ocr?.supported)}\n` +
`thinking${aiModelCapabilities?.thinking?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.thinking?.supported)}\n` +
`tools${aiModelCapabilities?.tools?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.tools?.supported)}\n` +
`audio${aiModelCapabilities?.audio?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.audio?.supported)}` +
"```";
const cmds = commands.filter(c => !(c instanceof ChatCommand));
+13 -8
View File
@@ -26,11 +26,11 @@ export class MistralChat extends ChatCommand {
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("match", match);
return this.executeMistral(msg, match?.[3]);
return this.executeMistral(msg, match?.[3] || "");
}
async executeMistral(msg: Message, text: string): Promise<void> {
if (!text || text.trim().length === 0) return;
if (!text || !text.trim().length) return;
const chatId = msg.chat.id;
@@ -63,7 +63,7 @@ export class MistralChat extends ChatCommand {
chatMessages.unshift({role: "system", content: [{type: "text", text: Environment.SYSTEM_PROMPT}]});
}
let waitMessage: Message;
let waitMessage: Message | null = null;
const startTime = Date.now();
@@ -74,7 +74,7 @@ export class MistralChat extends ChatCommand {
if (imagesCount) {
try {
const modelInfo = await commands.find(c => c instanceof MistralGetModel).getModelCapabilities();
const modelInfo = await commands.find(c => c instanceof MistralGetModel)?.getModelCapabilities();
if (modelInfo) {
if (!modelInfo.vision?.supported) {
await replyToMessage({
@@ -117,7 +117,7 @@ export class MistralChat extends ChatCommand {
await bot.editMessageText(
{
chat_id: chatId,
message_id: waitMessage.message_id,
message_id: <number>waitMessage?.message_id,
text: escapeMarkdownV2Text(text),
parse_mode: "MarkdownV2"
}
@@ -125,9 +125,11 @@ export class MistralChat extends ChatCommand {
console.log("editMessageText", text);
if (waitMessage) {
waitMessage.reply_to_message = msg;
waitMessage.text = text;
await MessageStore.put(waitMessage);
}
},
onStop: async () => {
}
@@ -172,9 +174,12 @@ export class MistralChat extends ChatCommand {
await replyToMessage({message: waitMessage, text: `⏱️ ${diff}s`});
}
}
} catch (error) {
logError(error);
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError);
} catch (e: any) {
logError(e);
if (waitMessage) {
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${e.toString()}`).catch(logError);
}
}
}
}
+2 -3
View File
@@ -6,7 +6,6 @@ import {Requirements} from "../base/requirements";
import {Requirement} from "../base/requirement";
import {mistralAi} from "../index";
import {AiModelCapabilities} from "../model/ai-model-capabilities";
import {BaseModelCard} from "@mistralai/mistralai/models/components/basemodelcard";
export class MistralGetModel extends Command {
title = "/mistralGetModel";
@@ -20,13 +19,13 @@ export class MistralGetModel extends Command {
async getModelCapabilities(): Promise<AiModelCapabilities | null> {
try {
const info: BaseModelCard = await mistralAi.models.retrieve({modelId: Environment.MISTRAL_MODEL}) as BaseModelCard;
const info = await mistralAi.models.retrieve({modelId: Environment.MISTRAL_MODEL}) as any;
console.log(info);
return {
vision: {supported: info.capabilities.vision},
ocr: {supported: info.capabilities.ocr},
thinking: null,
thinking: undefined,
tools: {supported: info.capabilities.functionCalling},
audio: {supported: info.capabilities.audioTranscription}
};
+39 -2
View File
@@ -4,7 +4,6 @@ import {Requirement} from "../base/requirement";
import {Message} from "typescript-telegram-bot-api";
import {mistralAi} from "../index";
import {logError, oldReplyToMessage, replyToMessage} from "../util/utils";
import {BaseModelCard} from "@mistralai/mistralai/models/components/basemodelcard";
export class MistralListModels extends Command {
title = "/mistralListModels";
@@ -21,7 +20,7 @@ export class MistralListModels extends Command {
console.log(listResponse);
const modelsString = listResponse.data
.sort((a, b) => a.name.localeCompare(b.name))
.sort((a, b) => a?.name?.localeCompare(b.name || "") || -1)
.map(e => `${e.id}`)
.join("\n");
@@ -38,3 +37,41 @@ export class MistralListModels extends Command {
}
}
}
type BaseModelCard = {
id: string;
object: string;
created?: number | undefined;
ownedBy: string;
/**
* This is populated by Harmattan, but some fields have a name
*
* @remarks
* that we don't want to expose in the API.
*/
capabilities: ModelCapabilities;
name?: string | null | undefined;
description?: string | null | undefined;
maxContextLength: number;
aliases?: Array<string> | undefined;
deprecation?: Date | null | undefined;
deprecationReplacementModel?: string | null | undefined;
defaultModelTemperature?: number | null | undefined;
type: "base";
};
type ModelCapabilities = {
completionChat: boolean;
functionCalling: boolean;
reasoning: boolean;
completionFim: boolean;
fineTuning: boolean;
vision: boolean;
ocr: boolean;
classification: boolean;
moderation: boolean;
audio: boolean;
audioTranscription: boolean;
audioTranscriptionRealtime: boolean;
audioSpeech: boolean;
};
+19 -14
View File
@@ -28,11 +28,12 @@ export class OllamaChat extends ChatCommand {
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
console.log("match", match);
return this.executeOllama(msg, match?.[3], match?.[1]?.toLowerCase()?.startsWith("ollamathink"));
return this.executeOllama(msg, match?.[3] || "", match?.[1]?.toLowerCase()?.startsWith("ollamathink"));
}
async executeOllama(msg: Message, text: string, think: boolean = false, voiceB64?: string): Promise<void> {
if ((!text || text.trim().length === 0) && !voiceB64) return;
async executeOllama(msg: Message, text: string, think: boolean = false, voiceB64?: string | null): Promise<void> {
if (!msg.from) return;
if ((!text || !text.trim().length) && !voiceB64) return;
const chatId = msg.chat.id;
@@ -66,7 +67,7 @@ export class OllamaChat extends ChatCommand {
chatMessages.unshift({role: "system", content: Environment.SYSTEM_PROMPT, images: []});
}
let waitMessage: Message;
let waitMessage: Message | null = null;
const startTime = Date.now();
@@ -77,7 +78,7 @@ export class OllamaChat extends ChatCommand {
if (!think && imagesCount) {
try {
const modelInfo = await commands.find(c => c instanceof OllamaGetModel).loadImageModelInfo();
const modelInfo = await commands.find(c => c instanceof OllamaGetModel)?.loadImageModelInfo();
if (modelInfo) {
if (!modelInfo.vision?.supported) {
await replyToMessage({
@@ -94,7 +95,7 @@ export class OllamaChat extends ChatCommand {
if (think) {
try {
const modelInfo = await commands.find(c => c instanceof OllamaGetModel).loadThinkModelInfo();
const modelInfo = await commands.find(c => c instanceof OllamaGetModel)?.loadThinkModelInfo();
if (modelInfo) {
if (!modelInfo.thinking?.supported) {
await replyToMessage({
@@ -131,11 +132,11 @@ export class OllamaChat extends ChatCommand {
}
const stream = await ollama.chat({
model: think ? Environment.OLLAMA_THINK_MODEL : imagesCount ? Environment.OLLAMA_IMAGE_MODEL : Environment.OLLAMA_MODEL,
model: <string>(think ? Environment.OLLAMA_THINK_MODEL : imagesCount ? Environment.OLLAMA_IMAGE_MODEL : Environment.OLLAMA_MODEL),
stream: true,
think: think,
messages: chatMessages,
options: options
options: <Partial<Options>>options
});
const newRequest = {
@@ -170,7 +171,7 @@ export class OllamaChat extends ChatCommand {
try {
await bot.editMessageText({
chat_id: chatId,
message_id: waitMessage.message_id,
message_id: <number>waitMessage?.message_id,
text: escapeMarkdownV2Text(text),
parse_mode: "MarkdownV2",
reply_markup: cancelMarkup
@@ -178,9 +179,11 @@ export class OllamaChat extends ChatCommand {
console.log("editMessageText", text);
if (waitMessage) {
waitMessage.reply_to_message = msg;
waitMessage.text = text;
await MessageStore.put(waitMessage);
}
} catch (e) {
logError(e);
}
@@ -225,7 +228,7 @@ export class OllamaChat extends ChatCommand {
shouldBreak = true;
}
if (getOllamaRequest(uuid).done) {
if (getOllamaRequest(uuid)?.done) {
shouldBreak = true;
}
@@ -266,17 +269,19 @@ export class OllamaChat extends ChatCommand {
}).catch(logError);
console.log(`aborted request ${uuid}:`, abortOllamaRequest(uuid));
}
} catch (error) {
if (error.message.toLowerCase().includes("aborted")) return;
} catch (e: any) {
if (e.message.toLowerCase().includes("aborted")) return;
logError(e);
if (waitMessage) {
await bot.editMessageReplyMarkup({
chat_id: chatId,
message_id: waitMessage.message_id,
reply_markup: {inline_keyboard: []}
}).catch(logError);
logError(error);
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError);
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${e.toString()}`).catch(logError);
}
}
}
}
+10 -8
View File
@@ -56,22 +56,24 @@ export class OllamaGetModel extends Command {
parse_mode: "Markdown"
}).catch(logError);
} catch (e) {
} catch (e: any) {
logError(e);
await replyToMessage({message: msg, text: e.toString()}).catch(logError);
}
}
private getModelText(model: string, info: AiModelCapabilities): string {
private getModelText(model: string | undefined, info: AiModelCapabilities | null): string {
return `model: ${model}\n\n` +
`vision: ${boolToEmoji(info.vision?.supported)}\n` +
`ocr: ${boolToEmoji(info.ocr?.supported)}\n` +
`thinking: ${boolToEmoji(info.thinking?.supported)}\n` +
`tools: ${boolToEmoji(info.tools?.supported)}\n` +
`audio: ${boolToEmoji(info.audio?.supported)}`;
`vision: ${boolToEmoji(info?.vision?.supported)}\n` +
`ocr: ${boolToEmoji(info?.ocr?.supported)}\n` +
`thinking: ${boolToEmoji(info?.thinking?.supported)}\n` +
`tools: ${boolToEmoji(info?.tools?.supported)}\n` +
`audio: ${boolToEmoji(info?.audio?.supported)}`;
}
async getModelCapabilities(model: string = Environment.OLLAMA_MODEL): Promise<AiModelCapabilities | null> {
async getModelCapabilities(model: string | undefined = Environment.OLLAMA_MODEL): Promise<AiModelCapabilities | null> {
if (!model) return null;
try {
const info = await ollama.show({model: model});
console.log(info);
+16 -10
View File
@@ -20,14 +20,16 @@ export class OllamaPrompt extends Command {
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("match", match);
return this.executeOllama(msg, match?.[3]);
return this.executeOllama(msg, match?.[3] || "");
}
async executeOllama(msg: Message, text: string): Promise<void> {
if (!text || text.trim().length === 0) return;
if (!text || !text.trim().length) return;
if (!msg.from) return;
const chatId = msg.chat.id;
let waitMessage: Message;
let waitMessage: Message | null = null;
const startTime = Date.now();
@@ -45,7 +47,7 @@ export class OllamaPrompt extends Command {
});
const stream = await ollama.generate({
model: Environment.OLLAMA_MODEL,
model: <string>Environment.OLLAMA_MODEL,
stream: true,
think: false,
prompt: text
@@ -83,7 +85,7 @@ export class OllamaPrompt extends Command {
try {
await bot.editMessageText({
chat_id: chatId,
message_id: waitMessage.message_id,
message_id: <number>waitMessage?.message_id,
text: escapeMarkdownV2Text(text),
parse_mode: "Markdown",
reply_markup: cancelMarkup
@@ -91,9 +93,11 @@ export class OllamaPrompt extends Command {
console.log("editMessageText", text);
if (waitMessage) {
waitMessage.reply_to_message = msg;
waitMessage.text = text;
await MessageStore.put(waitMessage);
}
} catch (e) {
logError(e);
}
@@ -138,7 +142,7 @@ export class OllamaPrompt extends Command {
shouldBreak = true;
}
if (getOllamaRequest(uuid).done) {
if (getOllamaRequest(uuid)?.done) {
shouldBreak = true;
}
@@ -173,17 +177,19 @@ export class OllamaPrompt extends Command {
reply_markup: {inline_keyboard: []}
}).catch(logError);
}
} catch (error) {
if (error.message.toLowerCase().includes("aborted")) return;
} catch (e: any) {
if (e.message.toLowerCase().includes("aborted")) return;
logError(e);
if (waitMessage) {
await bot.editMessageReplyMarkup({
chat_id: chatId,
message_id: waitMessage.message_id,
reply_markup: {inline_keyboard: []}
}).catch(logError);
logError(error);
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError);
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${e.toString()}`).catch(logError);
}
}
}
}
+6 -2
View File
@@ -4,7 +4,7 @@ import {Requirement} from "../base/requirement";
import {Message} from "typescript-telegram-bot-api";
import {bot, ollama} from "../index";
import {WebSearchResponse} from "../model/web-search-response";
import {oldEditMessageText, logError} from "../util/utils";
import {logError, oldEditMessageText} from "../util/utils";
import {Environment} from "../common/environment";
export class OllamaSearch extends Command {
@@ -18,6 +18,10 @@ export class OllamaSearch extends Command {
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
console.log("match", match);
const query = match?.[3] || "";
if (!query || !query.length) return;
const chatId = msg.chat.id;
try {
@@ -31,7 +35,7 @@ export class OllamaSearch extends Command {
parse_mode: "Markdown"
});
const results = await ollama.webSearch({query: match?.[3]});
const results = await ollama.webSearch({query: query});
console.log("results", results);
let message = "Результаты:\n\n";
+4 -3
View File
@@ -15,18 +15,19 @@ export class OllamaSetModel extends Command {
requirements = Requirements.Build(Requirement.BOT_CREATOR);
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
const newModel = match?.[3];
const newModel = match?.[3] || "";
if (!newModel || !newModel.length) return;
try {
await ollama.show({model: newModel});
Environment.setOllamaModel(newModel || Environment.OLLAMA_MODEL);
Environment.setOllamaModel(newModel || <string>Environment.OLLAMA_MODEL);
const text = newModel ? `Выбрана модель "${newModel}"`
: `Модель не задана. Будет использоваться стандартная модель "${Environment.OLLAMA_MODEL}".`;
await replyToMessage({message: msg, text: text}).catch(logError);
} catch (e) {
} catch (e: any) {
logError(e);
await replyToMessage({message: msg, text: e.toString()}).catch(logError);
}
+12 -7
View File
@@ -24,11 +24,11 @@ export class OpenAIChat extends ChatCommand {
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
console.log("OpenAI Chat: ", match);
return this.executeOpenAI(msg, match?.[3]);
return this.executeOpenAI(msg, match?.[3] || "");
}
async executeOpenAI(msg: Message, text: string): Promise<void> {
if (!text || text.trim().length === 0) return;
if (!text || !text.trim().length) return;
const chatId = msg.chat.id;
@@ -67,7 +67,7 @@ export class OpenAIChat extends ChatCommand {
});
}
let waitMessage: Message;
let waitMessage: Message | null = null;
const startTime = Date.now();
@@ -98,7 +98,7 @@ export class OpenAIChat extends ChatCommand {
await bot.editMessageText(
{
chat_id: chatId,
message_id: waitMessage.message_id,
message_id: <number>waitMessage?.message_id,
text: escapeMarkdownV2Text(text),
parse_mode: "MarkdownV2"
}
@@ -106,9 +106,11 @@ export class OpenAIChat extends ChatCommand {
console.log("editMessageText", text);
if (waitMessage) {
waitMessage.reply_to_message = msg;
waitMessage.text = text;
await MessageStore.put(waitMessage);
}
},
onStop: async () => {
}
@@ -156,12 +158,15 @@ export class OpenAIChat extends ChatCommand {
await replyToMessage({message: waitMessage, text: `⏱️ ${diff}s`});
}
}
} catch (error) {
logError(error);
} catch (e: any) {
logError(e);
if (waitMessage) {
await replyToMessage({
message: waitMessage,
text: `Произошла ошибка!\n${error.toString()}`
text: `Произошла ошибка!\n${e.toString()}`
}).catch(logError);
}
}
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ export class OpenAIGetModel extends Command {
try {
return {
vision: {supported: true},
ocr: null,
ocr: undefined,
thinking: {supported: true},
tools: {supported: true},
};
+1 -1
View File
@@ -61,7 +61,7 @@ export class Qr extends Command {
},
parse_mode: "HTML"
});
} catch (e) {
} catch (e: any) {
await replyToMessage({
message: msg,
text: `Не получилось сгенерировать QR: ${e?.message ?? String(e)}`
+7 -4
View File
@@ -48,6 +48,7 @@ export class Quote extends Command {
async execute(msg: Message): Promise<void> {
const chatId = msg.chat.id;
const reply = msg.reply_to_message;
if (!reply) return;
try {
const quoteRaw = (msg.quote?.text ?? reply.text ?? reply.caption ?? "").trim();
@@ -97,7 +98,9 @@ function twemojiUrl(emoji: string) {
return `https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/${code}.png`;
}
async function loadEmoji(emoji: string): Promise<CanvasImage> {
async function loadEmoji(emoji: string | undefined): Promise<CanvasImage | null> {
if (!emoji) return null;
const downloadAndCache = async (url: string): Promise<Image> => {
const res = await axios.get<ArrayBuffer>(url, {responseType: "arraybuffer"});
const img = await loadImage(Buffer.from(res.data));
@@ -491,7 +494,7 @@ async function drawLine(ctx: SKRSContext2D, line: Segment[], x: number, baseline
try {
const img = await loadEmoji(seg.v);
const y = baselineY - emojiSize + Math.round(fontSize * 0.2);
ctx.drawImage(img, cx, y, emojiSize, emojiSize);
ctx.drawImage(<Image>img, cx, y, emojiSize, emojiSize);
} catch (e) {
logError(e);
ctx.fillText(seg.v, cx, baselineY);
@@ -506,7 +509,7 @@ async function drawLine(ctx: SKRSContext2D, line: Segment[], x: number, baseline
} else {
const img = await loadEmoji("😥");
const y = baselineY - emojiSize + Math.round(fontSize * 0.2);
ctx.drawImage(img, cx, y, emojiSize, emojiSize);
ctx.drawImage(<Image>img, cx, y, emojiSize, emojiSize);
}
} catch (e) {
console.warn("Failed to draw custom emoji:", e);
@@ -514,7 +517,7 @@ async function drawLine(ctx: SKRSContext2D, line: Segment[], x: number, baseline
try {
const img = await loadEmoji("😥");
const y = baselineY - emojiSize + Math.round(fontSize * 0.2);
ctx.drawImage(img, cx, y, emojiSize, emojiSize);
ctx.drawImage(<Image>img, cx, y, emojiSize, emojiSize);
} catch (e) {
logError(e);
+3
View File
@@ -9,6 +9,9 @@ export class RandomInt extends Command {
description = "Ranged random integer from parameters";
async execute(msg: Message) {
// TODO: 01/05/2026, Danil Nikolaev: improve
if (!msg.text) return;
const split = msg.text.split(" ");
const min = parseInt(split[1]);
const max = parseInt(split[2]);
+3
View File
@@ -9,6 +9,9 @@ export class RandomString extends Command {
description = "literally random string (up to 4096 symbols)";
async execute(msg: Message) {
// TODO: 01/05/2026, Danil Nikolaev: improve
if (!msg.text) return;
const split = msg.text.split(" ");
const l = parseInt(split.length > 1 ? split[1] : "1");
+1 -1
View File
@@ -8,6 +8,6 @@ export class Start extends Command {
description = "Start the bot";
async execute(msg: Message): Promise<void> {
await commands.find(e => e instanceof Help).execute(msg);
await commands.find(e => e instanceof Help)?.execute(msg);
}
}
+3 -1
View File
@@ -81,12 +81,14 @@ export class Transliteration extends Command {
description = "Transliteration EN <--> RU";
async execute(msg: Message): Promise<void> {
if (!msg.text && !msg.caption) return;
let text: string = "";
if (msg.reply_to_message) {
text = (msg.reply_to_message.text || msg.reply_to_message.caption || "");
} else {
const split = (msg.text || msg.caption).split("/tr ");
const split = (<string>(msg.text || msg.caption)).split("/tr ");
if (split.length > 1) {
text = split[1].trim();
}
+2 -2
View File
@@ -19,7 +19,7 @@ export class Unban extends Command {
);
async execute(msg: Message) {
if (!msg.reply_to_message) return;
if (!msg.reply_to_message || !msg.reply_to_message.from) return;
const user = msg.reply_to_message.from;
const userId = user.id;
@@ -34,7 +34,7 @@ export class Unban extends Command {
return;
}
if (msg.from.id !== Environment.CREATOR_ID && Environment.ADMIN_IDS.has(userId)) {
if (msg.from?.id !== Environment.CREATOR_ID && Environment.ADMIN_IDS.has(userId)) {
await oldReplyToMessage(msg, "Админимтраторы бота и так не в бане.").catch(logError);
return;
}
+1 -1
View File
@@ -18,7 +18,7 @@ export class Unignore extends Command {
);
async execute(msg: Message) {
if (!msg.reply_to_message) return;
if (!msg.reply_to_message || !msg.reply_to_message.from) return;
const id = msg.reply_to_message.from.id;
const text = fullName(msg.reply_to_message.from);
-66
View File
@@ -1,66 +0,0 @@
import {Command} from "../base/command";
import {Message} from "typescript-telegram-bot-api";
import {editMessageText, logError, replyToMessage} from "../util/utils";
import {bot, botUser} from "../index";
import {DownloadOptions, downloadVideoFromYouTube, getYouTubeVideoId} from "../util/ytdl";
import {Environment} from "../common/environment";
import {TryAgain} from "../callback_commands/try-again";
export class YouTubeDownload extends Command {
command = ["ytdl", "youtube"];
argsMode = "required" as const;
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
const url = match?.[3];
return this.downloadYouTubeVideo(msg, {url: url});
}
async downloadYouTubeVideo(msg: Message, options: DownloadOptions): Promise<void> {
// TODO: 02.03.2026, Danil Nikolaev: add check for date
let waitMessage: Message | null = (msg.from.id === botUser.id) ? msg : null;
const videoId = "videoId" in options ? options.videoId : getYouTubeVideoId(options.url);
try {
if (!waitMessage) {
waitMessage = await replyToMessage({message: msg, text: "⏳ Скачиваю видео..."});
} else {
await editMessageText({message: msg, text: "⏳ Скачиваю видео..."});
}
const {time, exists, buffer} = await downloadVideoFromYouTube({videoId: videoId});
if (buffer) {
const start = Date.now();
waitMessage = await bot.editMessageMedia({
chat_id: msg.chat.id,
message_id: waitMessage.message_id,
media: {
type: "video",
media: buffer
}
}) as Message;
const diff = Date.now() - start;
waitMessage = await bot.editMessageCaption({
chat_id: msg.chat.id,
message_id: waitMessage.message_id,
caption: "✅ Видео" + (exists ? " загружено из кэша" : " успешно скачано") + " за " + (time + diff) + "мс",
}) as Message;
}
} catch (e) {
logError(e);
if (waitMessage && "text" in waitMessage) {
await bot.editMessageText({
chat_id: msg.chat.id,
message_id: waitMessage.message_id,
text: Environment.errorText,
reply_markup: {
inline_keyboard: [[
TryAgain.withData("/ytdl " + videoId).asButton()
]]
}
});
}
}
}
}
+1 -1
View File
@@ -67,7 +67,7 @@ export class Environment {
static ollamaCancelledText = "```Ollama\n❌ Отменено```";
static load() {
Environment.BOT_TOKEN = process.env.BOT_TOKEN;
Environment.BOT_TOKEN = <string>process.env.BOT_TOKEN;
Environment.TEST_ENVIRONMENT = ifTrue(process.env.TEST_ENVIRONMENT);
Environment.CHAT_IDS_WHITELIST = new Set(process.env.CHAT_IDS_WHITELIST?.split(",")?.map(e => parseInt(e.trim(), 10)) || []);
Environment.BOT_PREFIX = process.env.BOT_PREFIX || "";
+8 -4
View File
@@ -15,14 +15,16 @@ export class MessageStore {
}
static async put(m: Message | StoredMessage): Promise<StoredMessage> {
const maxSizePath = isStoredMessage(m) ? null : getPhotoMaxSize(m.photo)?.file_unique_id;
const msg: StoredMessage = isStoredMessage(m) ? m : {
chatId: m.chat.id,
id: m.message_id,
replyToMessageId: m.reply_to_message?.message_id ?? null,
fromId: m.from.id,
replyToMessageId: m.reply_to_message?.message_id,
fromId: <number>m.from?.id,
text: extractTextMessage(m),
date: m.date ?? 0,
photoMaxSizeFilePath: m.photo ? [getPhotoMaxSize(m.photo).file_unique_id] : null
photoMaxSizeFilePath: maxSizePath ? [maxSizePath] : undefined,
};
this.map.set(this.key(msg.chatId, msg.id), msg);
@@ -30,7 +32,9 @@ export class MessageStore {
return msg;
}
static async get(chatId: number, messageId: number): Promise<StoredMessage | null> {
static async get(chatId: number, messageId: number | undefined): Promise<StoredMessage | null> {
if (!messageId) return null;
const message = await messageDao.getById({chatId: chatId, id: messageId});
if (!message) return null;
+2 -2
View File
@@ -98,8 +98,8 @@ export class MessageDao extends Dao<StoredMessage> {
return messages.map(m => {
return {
chatId: m.chatId,
id: m.id,
replyToMessageId: m.replyToMessageId,
id: <number>m.id,
replyToMessageId: m.replyToMessageId || undefined,
fromId: m.fromId,
text: m.text,
date: m.date,
+1 -1
View File
@@ -93,7 +93,7 @@ export class UserDao extends Dao<StoredUser> {
mapFrom(users: UserInsert[]): StoredUser[] {
return users.map(u => {
return {
id: u.id,
id: <number>u.id,
isBot: u.isBot === 1,
firstName: u.firstName,
lastName: u.lastName,
+4 -11
View File
@@ -1,6 +1,6 @@
import "dotenv/config";
import {Environment} from "./common/environment";
import {TelegramBot, User} from "typescript-telegram-bot-api";
import {BotCommand, TelegramBot, User} from "typescript-telegram-bot-api";
import {Command} from "./base/command";
import {
delay,
@@ -67,7 +67,6 @@ import {GeminiGetModel} from "./commands/gemini-get-model";
import {GeminiSetModel} from "./commands/gemini-set-model";
import {Debug} from "./commands/debug";
import {GeminiGenerateImage} from "./commands/gemini-generate-image";
import {YouTubeDownload} from "./commands/youtube-download";
import fs from "node:fs";
import path from "node:path";
import {setInterval} from "node:timers";
@@ -79,8 +78,6 @@ import {OpenAISetModel} from "./commands/openai-set-model";
import {Info} from "./commands/info";
import {OpenAIGenImage} from "./commands/openai-gen-image";
import {clearUpFolderFromOldFiles} from "./util/files";
import {DownloadYtVideo} from "./callback_commands/download-yt-video";
import {YtInfo} from "./callback_commands/yt-info";
import {AdminsList} from "./commands/admins-list";
import {ExportDb} from "./commands/export-db";
@@ -106,7 +103,7 @@ export const ollama = new Ollama({
export const ollamaRequests: OllamaRequest[] = [];
export function getOllamaRequest(uuid: string): OllamaRequest | null {
export function getOllamaRequest(uuid: string): OllamaRequest | undefined {
return ollamaRequests.find(r => r.uuid === uuid);
}
@@ -169,8 +166,6 @@ export const commands: Command[] = [
new Shutdown(),
new Leave(),
new YouTubeDownload()
];
if (Environment.ENABLE_UNSAFE_EVAL) {
@@ -179,8 +174,6 @@ if (Environment.ENABLE_UNSAFE_EVAL) {
export const callbackCommands: CallbackCommand[] = [
new OllamaCancel(),
new DownloadYtVideo(),
new YtInfo()
];
if (Environment.OLLAMA_ADDRESS && Environment.OLLAMA_MODEL) {
@@ -288,10 +281,10 @@ async function main() {
return cmd.title && cmd.title.startsWith("/") && cmd.title.split(" ").length === 1 && cmd.description;
}).map(cmd => {
return {
command: cmd.title.toLowerCase(),
command: cmd.title?.toLowerCase() || "",
description: cmd.description,
};
});
}) as BotCommand[];
try {
const results = await Promise.all(
+2 -2
View File
@@ -3,7 +3,7 @@ export type StoredMessage = {
id: number;
replyToMessageId?: number;
fromId: number;
text?: string;
text?: string | null;
date: number;
photoMaxSizeFilePath?: string[];
photoMaxSizeFilePath?: string[] | null;
};
+2 -2
View File
@@ -2,7 +2,7 @@ export type StoredUser = {
id: number;
isBot: boolean;
firstName: string;
lastName?: string;
userName?: string;
lastName?: string | null;
userName?: string | null;
isPremium?: boolean;
}
+65 -175
View File
@@ -32,8 +32,6 @@ import {MessageStore} from "../common/message-store";
import {SystemInfo} from "../commands/system-info";
import {PrefixResponse} from "../commands/prefix-response";
import {OllamaChat} from "../commands/ollama-chat";
import {getYouTubeVideoId, getYouTubeVideoInfo, isVideoExists} from "./ytdl";
import {YouTubeDownload} from "../commands/youtube-download";
import {ChatCommand} from "../base/chat-command";
import {WebSearchResponse} from "../model/web-search-response";
import {GeminiChat} from "../commands/gemini-chat";
@@ -47,9 +45,6 @@ import {MistralGetModel} from "../commands/mistral-get-model";
import {OpenAIGetModel} from "../commands/openai-get-model";
import {SendOptions} from "../model/send-options";
import {EditOptions} from "../model/edit-options";
import VideoInfo from "youtubei.js/dist/src/parser/youtube/VideoInfo";
import {DownloadYtVideo} from "../callback_commands/download-yt-video";
import {TryAgain} from "../callback_commands/try-again";
import {StoredUser} from "../model/stored-user";
import {performFFmpeg} from "./ffmpeg";
@@ -68,7 +63,7 @@ export const ignoreIfMarkupFailed = (e: Error | TelegramError) => {
}
};
export const logError = (e: Error | TelegramError | string) => {
export const logError = (e: Error | TelegramError | string | unknown) => {
console.error(e);
};
@@ -91,7 +86,7 @@ export const isMessageTooLong = (e: Error | TelegramError) => {
export function searchChatCommand(
commands: Command[],
text: string,
botUsername: string = botUser.username
botUsername: string | undefined = botUser.username
): Command | null {
for (const command of commands) {
const match = command.finalRegexp.exec(text);
@@ -129,7 +124,7 @@ export async function checkRequirements(cmd: Command | CallbackCommand | null, m
let title: string;
if (isChatCommand) {
title = cmd.title;
title = cmd.title || "";
} else if (isCallbackCommand) {
title = cmd.data;
} else {
@@ -138,7 +133,7 @@ export async function checkRequirements(cmd: Command | CallbackCommand | null, m
const cbId = cb?.id;
const chatId = msg?.chat?.id || cb?.message?.chat?.id || -1;
const messageId = msg?.message_id || (cb && cb.message && "reply_to_message" in cb.message ? cb.message.reply_to_message.message_id : null) || -1;
const messageId = msg?.message_id || (cb && cb.message && "reply_to_message" in cb.message ? cb.message.reply_to_message?.message_id : null) || -1;
const fromId = msg?.from?.id || cb?.from?.id || -1;
const chatType = msg?.chat?.type || cb?.message?.chat?.type || null;
@@ -162,7 +157,7 @@ export async function checkRequirements(cmd: Command | CallbackCommand | null, m
await replyToMessage({chat_id: chatId, message_id: messageId, text: text});
} else if (cb) {
await bot.answerCallbackQuery({
callback_query_id: cbId,
callback_query_id: cbId || "",
text: text,
cache_time: 0,
show_alert: true
@@ -182,7 +177,7 @@ export async function checkRequirements(cmd: Command | CallbackCommand | null, m
return false;
}
if (reqs.isRequiresChat() && msg.chat.type === "private") {
if (reqs.isRequiresChat() && msg?.chat?.type === "private") {
console.log(`${title}: chatId is bad`);
await notifyUser("Тут Вам не чат.");
return false;
@@ -215,13 +210,13 @@ export async function checkRequirements(cmd: Command | CallbackCommand | null, m
}
if (reqs.isRequiresSameUser()) {
let originalFromId: number | null;
let originalFromId: number | undefined;
try {
const originalMessage = await MessageStore.get(chatId, messageId);
originalFromId = originalMessage?.fromId;
} catch (e) {
logError(e);
originalFromId = null;
originalFromId = undefined;
}
if (originalFromId && fromId !== originalFromId && fromId !== Environment.CREATOR_ID) {
@@ -239,7 +234,7 @@ export async function executeChatCommand(cmd: Command | null, msg: Message, text
if (!await checkRequirements(cmd, msg)) return false;
await cmd.execute(msg, cmd.regexp.exec(text));
await cmd.execute(msg, cmd.regexp?.exec(text));
return true;
}
@@ -249,7 +244,7 @@ export async function findAndExecuteCallbackCommand(commands: CallbackCommand[],
const cmd = searchCallbackCommand(commands, data);
if (!cmd) return false;
if (!await checkRequirements(cmd, null, query)) return false;
if (!await checkRequirements(cmd, undefined, query)) return false;
await cmd.execute(query);
await cmd.answerCallbackQuery(query);
@@ -281,7 +276,7 @@ export async function editMessageText(options: EditOptions) {
link_preview_options: options.link_preview_options,
});
return Promise.resolve(message);
} catch (e) {
} catch (e: any) {
logError(e);
if (isMarkupFailed(e)) {
@@ -295,6 +290,8 @@ export async function editMessageText(options: EditOptions) {
return Promise.reject(e);
}
}
return Promise.resolve(false);
}
export async function oldSendMessage(message: Message, text: string, parseMode?: ParseMode): Promise<Message> {
@@ -337,7 +334,7 @@ export async function replyToMessage(options: SendOptions): Promise<Message> {
text: options.text,
parse_mode: options.parse_mode,
reply_parameters: {
message_id: "message" in options ? options.message.message_id : options.message_id
message_id: <number>("message" in options ? options.message.message_id : options.message_id)
},
link_preview_options: options.link_preview_options
});
@@ -1177,12 +1174,13 @@ export function extractTextMessage(msg: Message | StoredMessage | string): strin
if (!msg) return null;
if (typeof msg === "string") return msg;
const text = (isStoredMessage(msg) ? msg.text : msg.text || msg.caption || "").trim();
if (text.length === 0) return null;
const text = (isStoredMessage(msg) ? msg.text : msg.text || msg.caption || "")?.trim();
if (!text || !text?.length) return null;
return text;
}
export function cutPrefixes(msg: Message | StoredMessage | string): string {
export function cutPrefixes(msg: Message | StoredMessage | string | null): string | null {
if (!msg) return null;
const chatCommands = commands.filter(c => c instanceof ChatCommand);
const prefixes = [Environment.BOT_PREFIX];
@@ -1193,11 +1191,13 @@ export function cutPrefixes(msg: Message | StoredMessage | string): string {
chatCommands.forEach((cmd) => {
const command = cmd.command;
if (command) {
if (Array.isArray(command)) {
command.forEach(pushPrefix);
} else {
pushPrefix(command);
}
}
});
const text = extractTextMessage(msg);
@@ -1216,10 +1216,10 @@ export function cutPrefixes(msg: Message | StoredMessage | string): string {
}
export function isStoredMessage(msg: Message | StoredMessage | null): msg is StoredMessage {
return msg && "id" in msg;
return !!msg && "id" in msg;
}
export async function loadImagesIfExists(msg: Message | StoredMessage): Promise<string[] | null> {
export async function loadImagesIfExists(msg: Message | StoredMessage): Promise<string[] | null | undefined> {
if (isStoredMessage(msg)) {
return msg.photoMaxSizeFilePath;
}
@@ -1237,7 +1237,7 @@ export async function loadImagesIfExists(msg: Message | StoredMessage): Promise<
const maxSize = await mapPhotoSizeToMax(getPhotoMaxSize(msg.photo));
if (maxSize) {
let imageFilePath = path.join(photoDir, maxSize.unique_file_id + ".jpg");
let imageFilePath: string | null = path.join(photoDir, maxSize.unique_file_id + ".jpg");
if (!fs.existsSync(imageFilePath)) {
const res = await axios.get<ArrayBuffer>(maxSize.url, {responseType: "arraybuffer"});
const src = Buffer.from(res.data);
@@ -1268,7 +1268,7 @@ export async function loadImagesFromFileIds(sizes: PhotoSize[]): Promise<string[
const promises = sizes.filter(s => !fs.existsSync(photoPathByUniqueId(s.file_unique_id)))
.map(s => mapPhotoSizeToMax(s));
const maxSizes = await Promise.all(promises);
const maxSizes = (await Promise.all(promises)).filter(e => !!e);
const imagePromises = maxSizes.map((size) => {
return axios.get<ArrayBuffer>(size.url, {responseType: "arraybuffer"});
@@ -1287,15 +1287,17 @@ export async function loadImagesFromFileIds(sizes: PhotoSize[]): Promise<string[
return null;
}
});
const finalPaths = paths.filter(p => p);
finalPaths.unshift(...existing);
const finalPaths = existing.concat(...paths.filter(p => !!p).map(p => <string>p));
return finalPaths;
}
export async function collectReplyChainText(triggerMsg: Message | StoredMessage, limit: number = 40, includeTrigger = true, cutPrefix: boolean = true): Promise<MessagePart[]> {
export async function collectReplyChainText(triggerMsg: Message | StoredMessage | null, limit: number = 40, includeTrigger = true, cutPrefix: boolean = true): Promise<MessagePart[]> {
if (!triggerMsg) return [];
const parts: MessagePart[] = [];
const pushPart = async (msg: Message | StoredMessage, textRequired: boolean = false) => {
const pushPart = async (msg: Message | StoredMessage | undefined | null, textRequired: boolean = false) => {
if (msg) {
const rawText = extractTextMessage(msg);
const cleanText = cutPrefix ? cutPrefixes(rawText) : rawText;
const imageNames = await loadImagesIfExists(msg);
@@ -1303,9 +1305,9 @@ export async function collectReplyChainText(triggerMsg: Message | StoredMessage,
if (!cleanText && textRequired) return;
if (!cleanText && !imageNames?.length) return;
const fromId = isStoredMessage(msg) ? msg.fromId : msg.from.id;
const fromId = isStoredMessage(msg) ? msg.fromId : msg.from?.id;
const firstName = isStoredMessage(msg) ?
(await UserStore.get(msg.fromId))?.firstName : msg.from.first_name;
(await UserStore.get(msg.fromId))?.firstName : msg.from?.first_name;
const images = imageNames ? imageNames.map(n => {
const filePath = photoPathByUniqueId(n);
@@ -1318,6 +1320,7 @@ export async function collectReplyChainText(triggerMsg: Message | StoredMessage,
name: firstName,
images: images ? images : []
});
}
};
const chatId = isStoredMessage(triggerMsg) ? triggerMsg.chatId as number : triggerMsg.chat.id;
@@ -1428,7 +1431,8 @@ export async function waveDistortSharp(
.toBuffer();
}
export async function downloadTelegramFile(filePath: string): Promise<Buffer> {
export async function downloadTelegramFile(filePath?: string | null): Promise<Buffer | null> {
if (!filePath) return null;
const url = `https://api.telegram.org/file/bot${Environment.BOT_TOKEN}/${filePath}`;
const res = await fetch(url);
if (!res.ok) throw new Error(`Failed to download file: ${res.status} ${res.statusText}`);
@@ -1606,7 +1610,7 @@ export function startIntervalEditor(params: {
try {
await params.editFn(next);
lastSent = next;
} catch (e) {
} catch (e: any) {
if ((e?.description ?? e?.message ?? "").includes("message is not modified")) return;
logError("edit failed: " + e);
}
@@ -1624,7 +1628,7 @@ export function startIntervalEditor(params: {
};
}
export function boolToInt(bool: boolean): number {
export function boolToInt(bool: boolean | undefined): number {
return bool ? 1 : 0;
}
@@ -1645,7 +1649,7 @@ export function buildExcludedSet<
const entries = Object.keys(cols)
.filter((key) => !excludeSet.has(key))
.map((key) => {
const realName = (cols as unknown)[key].name; // actual DB column name
const realName = (cols as any)[key].name; // actual DB column name
return [key, sql.raw(`excluded.${realName}`)] as const;
});
@@ -1674,7 +1678,7 @@ export function getRuntimeInfo(): RuntimeInfo {
export type PhotoMaxSize = { width: number, height: number, url: string; file_id: string; unique_file_id: string; };
export function getPhotoMaxSize(photos: PhotoSize[], target: number = Environment.MAX_PHOTO_SIZE): PhotoSize | null {
export function getPhotoMaxSize(photos: PhotoSize[] | undefined, target: number = Environment.MAX_PHOTO_SIZE): PhotoSize | null {
if (!photos) return null;
photos = photos.filter(p => Math.max(p.width, p.height) <= target);
@@ -1687,12 +1691,11 @@ export function getPhotoMaxSize(photos: PhotoSize[], target: number = Environmen
return photos.reduce((prev, cur) => {
if (!prev) return cur;
return cur.width * cur.height > prev.width * prev.height ? cur : prev;
}, null);
});
}
export async function mapPhotoSizeToMax(size: PhotoSize): Promise<PhotoMaxSize | null> {
export async function mapPhotoSizeToMax(size: PhotoSize | null): Promise<PhotoMaxSize | null> {
if (!size) return null;
return {
width: size.width,
@@ -1733,7 +1736,7 @@ export function boolToEmoji(bool: boolean | undefined): string {
export const albumCache = new Map<string, { messages: Message[], timer: NodeJS.Timeout }>();
async function processAlbum(groupId: string): Promise<string[]> {
async function processAlbum(groupId: string): Promise<string[] | undefined | null> {
const entry = albumCache.get(groupId);
if (!entry) return;
@@ -1741,10 +1744,10 @@ async function processAlbum(groupId: string): Promise<string[]> {
.filter(m => m.photo)
.map(m => m.photo);
const allPhotoMaxSizes = await Promise.all(allPhotos.map(photo => getPhotoMaxSize(photo)));
const allPhotoMaxSizes = await Promise.all(allPhotos.map(photo => getPhotoMaxSize(photo)).filter(s => !!s));
const ids = await loadImagesFromFileIds(allPhotoMaxSizes);
console.log(`Received album ${groupId} with ${ids.length} photos.`);
console.log(`Received album ${groupId} with ${ids?.length} photos.`);
console.log("File IDs:", ids);
albumCache.delete(groupId);
@@ -1755,7 +1758,7 @@ export function photoPathByUniqueId(uniqueId: string): string {
return path.join(photoDir, uniqueId + ".jpg");
}
export function getCurrentModel(): string {
export function getCurrentModel(): string | undefined {
switch (Environment.DEFAULT_AI_PROVIDER) {
case AiProvider.OLLAMA:
return Environment.OLLAMA_MODEL;
@@ -1769,7 +1772,7 @@ export function getCurrentModel(): string {
}
export async function getCurrentModelCapabilities(): Promise<AiModelCapabilities | null> {
let promise: Promise<AiModelCapabilities | null> = null;
let promise: Promise<AiModelCapabilities | null> | null | undefined = null;
switch (Environment.DEFAULT_AI_PROVIDER) {
case AiProvider.OLLAMA: {
const ollamaGetModel = commands.find(c => c instanceof OllamaGetModel);
@@ -1779,13 +1782,14 @@ export async function getCurrentModelCapabilities(): Promise<AiModelCapabilities
promise = new Promise(async (resolve, reject) => {
try {
const defaultModelCapabilities = await ollamaGetModel.getModelCapabilities();
const imageModelCapabilities = await ollamaGetModel.loadImageModelInfo();
const result = {
vision: (await ollamaGetModel.loadImageModelInfo()).vision,
ocr: null,
thinking: (await ollamaGetModel.loadThinkModelInfo()).thinking,
tools: defaultModelCapabilities.tools,
audio: defaultModelCapabilities.audio
vision: imageModelCapabilities?.vision,
ocr: imageModelCapabilities?.ocr,
thinking: (await ollamaGetModel.loadThinkModelInfo())?.thinking,
tools: defaultModelCapabilities?.tools,
audio: defaultModelCapabilities?.audio
};
resolve(result);
} catch (e) {
@@ -1824,6 +1828,7 @@ export async function processMyChatMember(u: ChatMemberUpdated): Promise<void> {
export async function processNewMessage(msg: Message): Promise<void> {
console.log("New Message", msg);
if (!msg.from) return;
const envFile: string = fs.readFileSync(".env").toString();
const env = new Map(
@@ -2002,14 +2007,16 @@ export async function processNewMessage(msg: Message): Promise<void> {
const photos = await processAlbum(groupId);
console.log("processedAlbum", photos);
if (storedMsg) {
storedMsg.photoMaxSizeFilePath = photos;
await MessageStore.put(storedMsg).catch(logError);
}
resolve(true);
}, 1000)
});
} else {
const entry = albumCache.get(groupId);
entry.messages.push(msg);
entry?.messages?.push(msg);
}
});
}
@@ -2044,9 +2051,7 @@ export async function processNewMessage(msg: Message): Promise<void> {
const textToCheck = startsWithPrefix ? messageWithoutPrefix : cmdText;
if (Environment.PROCESS_LINKS && await processYouTubeLink(msg, getFirstLink(msg))) return;
if (msg.chat.type !== "private" && (!msg.reply_to_message || msg.reply_to_message.from.id !== botUser.id) && !startsWithPrefix && !msg.voice) return;
if (msg.chat.type !== "private" && (!msg.reply_to_message || msg.reply_to_message.from?.id !== botUser.id) && !startsWithPrefix && !msg.voice) return;
if (msg.chat.type === "private" && !Environment.ADMIN_IDS.has(msg.chat.id)) return;
@@ -2060,6 +2065,7 @@ export async function processNewMessage(msg: Message): Promise<void> {
const input = path.join(Environment.DATA_PATH, "input.ogg");
const output = path.join(Environment.DATA_PATH, "output.wav")
if (fileBuffer) {
try {
fs.writeFileSync(input, fileBuffer);
await performFFmpeg(() =>
@@ -2075,152 +2081,36 @@ export async function processNewMessage(msg: Message): Promise<void> {
voiceB64 = fileBuffer.toString("base64");
fs.rmSync(input);
fs.rmSync(output);
} catch (e) {
logError(e);
}
}
}
switch (Environment.DEFAULT_AI_PROVIDER) {
case AiProvider.OLLAMA: {
await commands.find(e => e instanceof OllamaChat).executeOllama(msg, textToCheck, false, voiceB64);
await commands.find(e => e instanceof OllamaChat)?.executeOllama(msg, textToCheck, false, voiceB64);
break;
}
case AiProvider.GEMINI: {
await commands.find(e => e instanceof GeminiChat).executeGemini(msg, textToCheck);
await commands.find(e => e instanceof GeminiChat)?.executeGemini(msg, textToCheck);
break;
}
case AiProvider.MISTRAL: {
await commands.find(e => e instanceof MistralChat).executeMistral(msg, textToCheck);
await commands.find(e => e instanceof MistralChat)?.executeMistral(msg, textToCheck);
break;
}
case AiProvider.OPENAI: {
await commands.find(e => e instanceof OpenAIChat).executeOpenAI(msg, textToCheck);
await commands.find(e => e instanceof OpenAIChat)?.executeOpenAI(msg, textToCheck);
break;
}
}
}
function getFirstLink(msg: Message): string | null {
if (msg.entities) {
const urlEntities = msg.entities.filter(e => e.type === "url");
if (urlEntities.length) {
const e = urlEntities[0];
return msg.text.substring(e.offset, e.offset + e.length);
}
}
return null;
}
export async function processYouTubeLink(msg: Message, url?: string, id?: string): Promise<boolean> {
if (!url && !id) return false;
let waitMessage: Message | null = msg.from.id === botUser.id ? msg : null;
let videoId: string | null = null;
try {
try {
videoId = id || getYouTubeVideoId(url);
} catch (e) {
logError(e);
return false;
}
const yt = commands.find(e => e instanceof YouTubeDownload);
if (await checkRequirements(yt, msg)) {
if (!waitMessage) {
waitMessage = await replyToMessage({
message: msg,
text: "⏳ Ищу информацию о видео..."
});
} else {
await editMessageText({message: msg, text: "⏳ Ищу информацию о видео..."});
}
let videoInfo: VideoInfo | null = null;
let ytError: string = null;
try {
videoInfo = await getYouTubeVideoInfo(videoId);
} catch (e) {
logError(e);
if ("version" in e) {
ytError = e.message;
}
}
console.log("VIDEO_INFO", videoInfo);
let text: string = null;
const inCache = isVideoExists({videoId: videoId});
const duration = videoInfo?.basic_info?.duration || null;
const canDownload = inCache || duration && duration <= 300;
if (videoInfo) {
text = "Видео с YouTube\n\n" +
`Название: ${videoInfo.basic_info?.title}\n` +
`Автор: ${videoInfo.secondary_info?.owner?.author?.name}\n` +
`Длительность: ${duration} сек.`;
if (!canDownload) {
text += `\n\nВидео слишком длинное (${duration} сек. > 300 сек.)`;
}
} else if (!ytError) {
text = "Информация о видео не найдена";
}
const errorButInCache = !videoInfo && ytError && inCache;
if (errorButInCache) {
text = "Я не смог получить информацию о видео, но нашёл его в кэше.";
}
if (!text && ytError) {
await editMessageText({
message: waitMessage,
text: Environment.errorText,
reply_markup: {
inline_keyboard: [[
TryAgain.withData("/ytinfo " + videoId).asButton()
]]
}
});
} else {
await editMessageText({
message: waitMessage,
text: text,
reply_markup: canDownload ? {
inline_keyboard: [[
DownloadYtVideo.withData(inCache, "/ytdl " + videoId).asButton()
]]
} : {inline_keyboard: []}
});
}
}
return true;
} catch (e) {
logError(e);
await editMessageText({
message: waitMessage,
text: Environment.errorText,
reply_markup: {
inline_keyboard: [[
TryAgain.withData("/ytinfo " + videoId).asButton()
]]
}
});
}
return false;
}
export async function processEditedMessage(msg: Message): Promise<void> {
console.log("Edited Message", msg);
if (!msg.from) return;
await UserStore.put(msg.from);
-169
View File
@@ -1,169 +0,0 @@
import fs from "node:fs";
import path from "node:path";
import {videoDir, videoTempDir} from "../index";
import ffmpeg from "fluent-ffmpeg";
import Innertube, {Platform, Types} from "youtubei.js";
import {Readable} from "node:stream";
import {logError} from "./utils";
import {performFFmpeg} from "./ffmpeg";
import VideoInfo from "youtubei.js/dist/src/parser/youtube/VideoInfo";
let innertube: Innertube | null = null;
export async function getYT(): Promise<Innertube> {
if (innertube) {
return innertube;
} else {
innertube = await Innertube.create({
generate_session_locally: true,
retrieve_player: true
});
return innertube;
}
}
export function getYouTubeVideoId(url: string): string {
const regex = /(?:(?:youtube\.com|music\.youtube\.com)\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?|shorts|clip)\/|.*[?&]v=)|youtu\.be\/)([^"&?/\s]{11})/i;
const match = url.match(regex);
if (!match || !match[1]) throw new Error("Invalid YouTube or Shorts URL");
return match[1];
}
export async function getYouTubeVideoInfo(videoId: string): Promise<VideoInfo> {
try {
return (await getYT()).getInfo(videoId, {client: "ANDROID"});
} catch (e) {
logError(e);
}
}
export function isVideoExists(options: DownloadOptions): boolean {
const videoId = "videoId" in options ? options.videoId : getYouTubeVideoId(options.url);
const filePath = path.join(videoDir, `${videoId}.mp4`);
return fs.existsSync(filePath);
}
export function getVideoFromCache(videoId: string): Buffer | null {
if (!isVideoExists({videoId: videoId})) return null;
const filePath = path.join(videoDir, `${videoId}.mp4`);
return Buffer.from(fs.readFileSync(filePath));
}
export type DownloadOptions = {
url: string
} | {
videoId: string;
}
export async function downloadVideoFromYouTube(options: DownloadOptions): Promise<{
time: number,
exists?: boolean,
buffer: Buffer | null
}> {
const start = Date.now();
let buffer: Buffer | null = null;
try {
const videoId = "videoId" in options ? options.videoId : getYouTubeVideoId(options.url);
const filePath = path.join(videoDir, `${videoId}.mp4`);
if (fs.existsSync(filePath)) {
const buffer = Buffer.from(fs.readFileSync(filePath));
return {
time: Date.now() - start,
exists: true,
buffer: buffer
};
}
Platform.shim.eval = async (data: Types.BuildScriptResult, env: Record<string, Types.VMPrimative>) => {
const properties = [];
if (env.n) properties.push(`n: exportedVars.nFunction("${env.n}")`);
if (env.sig) properties.push(`sig: exportedVars.sigFunction("${env.sig}")`);
const code = `${data.output}\nreturn { ${properties.join(", ")} }`;
return new Function(code)();
};
const yt = await getYT();
const videoInfo = await yt.getInfo(videoId, {client: "ANDROID"});
console.log("Video info", videoInfo);
console.log(`Fetching metadata for: ${videoId}...`);
const targetQuality = "360p";
const videoFormat = videoInfo.streaming_data?.formats.find(f => f.quality_label.startsWith(targetQuality))
|| videoInfo.streaming_data?.adaptive_formats.find(f => f.quality_label.startsWith(targetQuality));
const audioFormat = videoInfo.chooseFormat({type: "audio", quality: "best", language: "original"});
console.log("Video format: ", videoFormat);
console.log("Audio Format: ", audioFormat);
if (!videoFormat) {
console.log(`Quality ${targetQuality} not found. Falling back to best available.`);
}
const videoWebStream = await videoInfo.download({
itag: videoFormat.itag,
client: "ANDROID"
});
const audioWebStream = await videoInfo.download({
itag: audioFormat.itag,
client: "ANDROID"
});
const videoStream = Readable.fromWeb(videoWebStream as any);
const audioStream = Readable.fromWeb(audioWebStream as any);
const videoPath = path.join(videoTempDir, `temp_video_${videoId}.mp4`);
const audioPath = path.join(videoTempDir, `temp_audio_${videoId}.mp4`);
const writeStream = (stream: any, path: string) =>
new Promise((resolve, reject) => {
const file = fs.createWriteStream(path);
stream.pipe(file);
file.on("finish", resolve);
file.on("error", reject);
});
await Promise.all([
writeStream(videoStream, videoPath),
writeStream(audioStream, audioPath)
]);
await performFFmpeg(() =>
ffmpeg()
.input(videoPath)
.input(audioPath)
.videoCodec("copy")
.audioCodec("copy")
.save(filePath)
.on("progress", (progress) => {
console.log("progress", progress);
})
).catch(logError);
fs.unlinkSync(videoPath);
fs.unlinkSync(audioPath);
buffer = fs.readFileSync(filePath);
console.log(`✅ Saved to ${videoId}.mp4`);
} catch (error) {
console.error("❌ Download failed:", error instanceof Error ? error.message : error);
throw error;
}
const end = Date.now();
const diff = end - start;
console.log(`Video downloaded.\ntook ${diff}ms`);
return {
time: diff,
buffer: buffer,
};
}
+14 -1
View File
@@ -4,7 +4,20 @@
"rootDir": "src",
"outDir": "dist",
"types": ["node"],
"skipLibCheck": true
"skipLibCheck": true,
"moduleResolution": "bundler",
"module": "esnext",
"target": "es2024",
"esModuleInterop": true,
"lib": ["dom", "dom.iterable", "ESNext"],
"allowJs": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]