Initial commit
This commit is contained in:
commit
840b3af2eb
10 changed files with 550 additions and 0 deletions
2
.env.example
Normal file
2
.env.example
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
BOT_TOKEN=your_telegram_bot_token_here
|
||||||
|
BOT_API_ROOT=
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.env
|
||||||
|
node_modules
|
||||||
|
assets
|
||||||
145
package-lock.json
generated
Normal file
145
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
{
|
||||||
|
"name": "bot",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@grammyjs/files": "^1.2.0",
|
||||||
|
"dotenv": "^17.4.2",
|
||||||
|
"grammy": "^1.42.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@grammyjs/files": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@grammyjs/files/-/files-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-UhQNGe2gpGgGMdrAUFXrZld3qQjt/3oYBHIo1aU0zIrKKvI5bgxzXSjPaJpx69HUO9mM7TbOaSsJL8lWbqnzJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || >=14.13.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"grammy": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@grammyjs/types": {
|
||||||
|
"version": "3.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.26.0.tgz",
|
||||||
|
"integrity": "sha512-jlnyfxfev/2o68HlvAGRocAXgdPPX5QabG7jZlbqC2r9DZyWBfzTlg+nu3O3Fy4EhgLWu28hZ/8wr7DsNamP9A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/abort-controller": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"event-target-shim": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "4.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "17.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||||
|
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/event-target-shim": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/grammy": {
|
||||||
|
"version": "1.42.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/grammy/-/grammy-1.42.0.tgz",
|
||||||
|
"integrity": "sha512-1AdCge+AkjSdp2FwfICSFnVbl8Mq3KVHJDy+DgTI9+D6keJ0zWALPRKas5jv/8psiCzL4N2cEOcGW7O45Kn39g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@grammyjs/types": "3.26.0",
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"debug": "^4.4.3",
|
||||||
|
"node-fetch": "^2.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || >=14.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
package.json
Normal file
10
package.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/index.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@grammyjs/files": "^1.2.0",
|
||||||
|
"dotenv": "^17.4.2",
|
||||||
|
"grammy": "^1.42.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
126
setup.md
Normal file
126
setup.md
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
# Setup
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Node.js 20+ or newer
|
||||||
|
- npm
|
||||||
|
- A Telegram bot token from `@BotFather`
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create `.env` from `.env.example` and fill in your bot token:
|
||||||
|
|
||||||
|
```env
|
||||||
|
BOT_TOKEN=your_telegram_bot_token_here
|
||||||
|
BOT_API_ROOT=
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start the bot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## What The Bot Does
|
||||||
|
|
||||||
|
- Saves text messages to `assets/request.txt` by appending new lines
|
||||||
|
- Saves photos, videos, voice messages, and documents to `assets/`
|
||||||
|
- Replies with `Ок` for supported message types
|
||||||
|
|
||||||
|
## Optional: Local Bot API Server For Files Larger Than 20 MB
|
||||||
|
|
||||||
|
Telegram's hosted Bot API cannot download files larger than `20 MB`. To support larger files, run a local `telegram-bot-api` server and point the bot to it with `BOT_API_ROOT`.
|
||||||
|
|
||||||
|
Official references:
|
||||||
|
|
||||||
|
- https://github.com/tdlib/telegram-bot-api
|
||||||
|
- https://core.telegram.org/bots/api#using-a-local-bot-api-server
|
||||||
|
- https://grammy.dev/guide/api.html
|
||||||
|
|
||||||
|
### 1. Get `api_id` and `api_hash`
|
||||||
|
|
||||||
|
Create them here:
|
||||||
|
|
||||||
|
- https://core.telegram.org/api/obtaining_api_id
|
||||||
|
|
||||||
|
### 2. Build The Local Bot API Server In `~/dev/lamda/bot_api`
|
||||||
|
|
||||||
|
The commands below match the local layout already prepared on this machine.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/dev/lamda/bot_api
|
||||||
|
|
||||||
|
~/miniconda3/bin/conda create -y \
|
||||||
|
-p ~/dev/lamda/bot_api/.buildenv \
|
||||||
|
--override-channels \
|
||||||
|
-c conda-forge \
|
||||||
|
gperf openssl zlib cmake
|
||||||
|
|
||||||
|
git clone --recursive https://github.com/tdlib/telegram-bot-api.git ~/dev/lamda/bot_api/telegram-bot-api
|
||||||
|
|
||||||
|
git -C ~/dev/lamda/bot_api/telegram-bot-api submodule update --init --depth 1 td
|
||||||
|
|
||||||
|
mkdir -p ~/dev/lamda/bot_api/telegram-bot-api/build
|
||||||
|
mkdir -p ~/dev/lamda/bot_api/install
|
||||||
|
|
||||||
|
export PATH=~/dev/lamda/bot_api/.buildenv/bin:$PATH
|
||||||
|
export CMAKE_PREFIX_PATH=~/dev/lamda/bot_api/.buildenv
|
||||||
|
|
||||||
|
cmake -S ~/dev/lamda/bot_api/telegram-bot-api \
|
||||||
|
-B ~/dev/lamda/bot_api/telegram-bot-api/build \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=~/dev/lamda/bot_api/install \
|
||||||
|
-DOPENSSL_ROOT_DIR=~/dev/lamda/bot_api/.buildenv \
|
||||||
|
-DZLIB_ROOT=~/dev/lamda/bot_api/.buildenv
|
||||||
|
|
||||||
|
cmake --build ~/dev/lamda/bot_api/telegram-bot-api/build -j"$(nproc)"
|
||||||
|
cmake --install ~/dev/lamda/bot_api/telegram-bot-api/build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start The Local Bot API Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/dev/lamda/bot_api/data
|
||||||
|
mkdir -p ~/dev/lamda/bot_api/tmp
|
||||||
|
|
||||||
|
~/dev/lamda/bot_api/install/bin/telegram-bot-api \
|
||||||
|
--api-id <API_ID> \
|
||||||
|
--api-hash <API_HASH> \
|
||||||
|
--local \
|
||||||
|
--http-port 8081 \
|
||||||
|
--dir ~/dev/lamda/bot_api/data \
|
||||||
|
--temp-dir ~/dev/lamda/bot_api/tmp
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Switch The Bot To The Local Server
|
||||||
|
|
||||||
|
Before switching away from Telegram's hosted Bot API, log the bot out there once:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://api.telegram.org/bot<BOT_TOKEN>/logOut
|
||||||
|
```
|
||||||
|
|
||||||
|
Then set this in `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
BOT_TOKEN=your_telegram_bot_token_here
|
||||||
|
BOT_API_ROOT=http://127.0.0.1:8081
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the bot after that:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `BOT_API_ROOT` is optional. Leave it empty to use the default Telegram hosted Bot API.
|
||||||
|
- The local Bot API server listens over HTTP by default.
|
||||||
|
- If both the bot and the local Bot API server run on the same machine, `127.0.0.1:8081` is enough.
|
||||||
22
src/bot.js
Normal file
22
src/bot.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
const { Bot } = require("grammy");
|
||||||
|
const { hydrateFiles } = require("@grammyjs/files");
|
||||||
|
const { registerMessageHandlers } = require("./handlers/message");
|
||||||
|
|
||||||
|
function createBot({ token, botApiRoot, assetStore }) {
|
||||||
|
const bot = new Bot(token, botApiRoot ? {
|
||||||
|
client: { apiRoot: botApiRoot },
|
||||||
|
} : undefined);
|
||||||
|
bot.api.config.use(hydrateFiles(bot.token));
|
||||||
|
|
||||||
|
registerMessageHandlers(bot, assetStore);
|
||||||
|
|
||||||
|
bot.catch((error) => {
|
||||||
|
console.error("Bot error:", error.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createBot,
|
||||||
|
};
|
||||||
19
src/config.js
Normal file
19
src/config.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
const path = require("node:path");
|
||||||
|
const dotenv = require("dotenv");
|
||||||
|
|
||||||
|
dotenv.config({ quiet: true });
|
||||||
|
|
||||||
|
const token = process.env.BOT_TOKEN;
|
||||||
|
const botApiRoot = process.env.BOT_API_ROOT;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("Missing BOT_TOKEN in .env");
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetsDir = path.join(__dirname, "..", "assets");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
assetsDir,
|
||||||
|
botApiRoot,
|
||||||
|
token,
|
||||||
|
};
|
||||||
77
src/handlers/message.js
Normal file
77
src/handlers/message.js
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
function getAttachmentPayload(message) {
|
||||||
|
if (message.photo) {
|
||||||
|
const largestPhoto = message.photo[message.photo.length - 1];
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileId: largestPhoto.file_id,
|
||||||
|
options: {
|
||||||
|
kind: "photo",
|
||||||
|
fallbackExtension: ".jpg",
|
||||||
|
caption: message.caption,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.video) {
|
||||||
|
return {
|
||||||
|
fileId: message.video.file_id,
|
||||||
|
options: {
|
||||||
|
kind: "video",
|
||||||
|
fallbackExtension: ".mp4",
|
||||||
|
caption: message.caption,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.voice) {
|
||||||
|
return {
|
||||||
|
fileId: message.voice.file_id,
|
||||||
|
options: {
|
||||||
|
kind: "voice",
|
||||||
|
fallbackExtension: ".ogg",
|
||||||
|
caption: message.caption,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.document) {
|
||||||
|
return {
|
||||||
|
fileId: message.document.file_id,
|
||||||
|
options: {
|
||||||
|
kind: "document",
|
||||||
|
originalName: message.document.file_name,
|
||||||
|
fallbackExtension: ".bin",
|
||||||
|
caption: message.caption,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerMessageHandlers(bot, assetStore) {
|
||||||
|
const deliveredReply = "Сообщение доставлено ИИ агенту, оно обрабатывается";
|
||||||
|
|
||||||
|
bot.on("message", async (ctx) => {
|
||||||
|
const { message } = ctx;
|
||||||
|
|
||||||
|
if (message.text) {
|
||||||
|
await assetStore.saveText(ctx, message.text);
|
||||||
|
await ctx.reply(deliveredReply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachment = getAttachmentPayload(message);
|
||||||
|
|
||||||
|
if (!attachment) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await assetStore.saveTelegramFile(ctx, attachment.fileId, attachment.options);
|
||||||
|
await ctx.reply(deliveredReply);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
registerMessageHandlers,
|
||||||
|
};
|
||||||
20
src/index.js
Normal file
20
src/index.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
const { assetsDir, botApiRoot, token } = require("./config");
|
||||||
|
const { createBot } = require("./bot");
|
||||||
|
const { createAssetStore } = require("./services/asset-store");
|
||||||
|
|
||||||
|
const assetStore = createAssetStore({ assetsDir });
|
||||||
|
const bot = createBot({ token, botApiRoot, assetStore });
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await assetStore.ensureDir();
|
||||||
|
await bot.start({
|
||||||
|
onStart: () => {
|
||||||
|
console.log(`Bot is running. Files are saved to ${assetsDir}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error("Failed to start bot:", error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
126
src/services/asset-store.js
Normal file
126
src/services/asset-store.js
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
const fs = require("node:fs/promises");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
function makeMessageKey(ctx, kind) {
|
||||||
|
const chatId = ctx.chat?.id ?? "unknown-chat";
|
||||||
|
const messageId = ctx.message?.message_id ?? Date.now();
|
||||||
|
|
||||||
|
return `${Date.now()}-${chatId}-${messageId}-${kind}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeBaseName(name) {
|
||||||
|
const baseName = path.parse(name).name;
|
||||||
|
const sanitized = baseName.replace(/[^a-zA-Z0-9._-]/g, "_").replace(/_+/g, "_");
|
||||||
|
|
||||||
|
return sanitized || "file";
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickExtension(filePath, originalName, fallbackExtension) {
|
||||||
|
return (
|
||||||
|
path.extname(originalName || "") ||
|
||||||
|
path.extname(filePath || "") ||
|
||||||
|
fallbackExtension
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRequestEntry(text, requestedAt = new Date()) {
|
||||||
|
const normalizedText = String(text).replace(/\r\n/g, "\n");
|
||||||
|
|
||||||
|
return `[${requestedAt.toISOString()}]\n${normalizedText}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRequestDate(ctx) {
|
||||||
|
const timestamp = ctx.message?.date;
|
||||||
|
|
||||||
|
if (typeof timestamp === "number") {
|
||||||
|
return new Date(timestamp * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRequestLogPath(assetsDir) {
|
||||||
|
return path.join(assetsDir, "request.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function appendRequestEntry(assetsDir, text, requestedAt) {
|
||||||
|
await fs.appendFile(
|
||||||
|
getRequestLogPath(assetsDir),
|
||||||
|
formatRequestEntry(text, requestedAt),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDownloadedFileEntry({ kind, targetPath, originalName, caption }) {
|
||||||
|
const lines = [
|
||||||
|
`downloaded ${kind}: ${path.basename(targetPath)}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (originalName) {
|
||||||
|
lines.push(`original name: ${originalName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (caption) {
|
||||||
|
lines.push(`caption: ${caption}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCaption(assetsDir, baseName, caption) {
|
||||||
|
if (!caption) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const captionPath = path.join(assetsDir, `${baseName}.txt`);
|
||||||
|
await fs.writeFile(captionPath, caption, "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAssetStore({ assetsDir }) {
|
||||||
|
return {
|
||||||
|
async ensureDir() {
|
||||||
|
await fs.mkdir(assetsDir, { recursive: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveText(ctx, text, kind = "text") {
|
||||||
|
const requestedAt = getRequestDate(ctx);
|
||||||
|
|
||||||
|
await appendRequestEntry(assetsDir, text, requestedAt);
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveTelegramFile(ctx, fileId, options) {
|
||||||
|
const telegramFile = await ctx.api.getFile(fileId);
|
||||||
|
const baseNameParts = [makeMessageKey(ctx, options.kind)];
|
||||||
|
const requestedAt = getRequestDate(ctx);
|
||||||
|
|
||||||
|
if (options.originalName) {
|
||||||
|
baseNameParts.push(sanitizeBaseName(options.originalName));
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseName = baseNameParts.join("-");
|
||||||
|
const extension = pickExtension(
|
||||||
|
telegramFile.file_path,
|
||||||
|
options.originalName,
|
||||||
|
options.fallbackExtension,
|
||||||
|
);
|
||||||
|
const targetPath = path.join(assetsDir, `${baseName}${extension}`);
|
||||||
|
|
||||||
|
await telegramFile.download(targetPath);
|
||||||
|
await saveCaption(assetsDir, baseName, options.caption);
|
||||||
|
await appendRequestEntry(
|
||||||
|
assetsDir,
|
||||||
|
formatDownloadedFileEntry({
|
||||||
|
kind: options.kind,
|
||||||
|
targetPath,
|
||||||
|
originalName: options.originalName,
|
||||||
|
caption: options.caption,
|
||||||
|
}),
|
||||||
|
requestedAt,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createAssetStore,
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue