From 9bdf4340dc03df847d4a312670c610295dac5786 Mon Sep 17 00:00:00 2001 From: Folist Date: Fri, 9 Oct 2026 23:13:07 +0300 Subject: [PATCH] =?UTF-8?q?1.3.0-beta.6:=20=D0=BF=D1=83=D0=BB=D1=8C=D1=82?= =?UTF-8?q?=20=C2=AB=D0=A4=D0=B0=D0=B9=D0=BB=D1=8B=C2=BB=20=E2=80=94=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BA=D1=81=D1=82=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20(=D1=87=D1=82?= =?UTF-8?q?=D0=BE=20=D0=B7=D0=B4=D0=B5=D1=81=D1=8C=20=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=82=D1=81=D1=8F=20=D0=B8=20=D1=81=D0=BA=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=BA=D0=BE),=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B,=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B5=D1=89=D1=91?= =?UTF-8?q?=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D1=8F=D1=8E=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=B8=D0=B7=20MAX,=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA?= =?UTF-8?q?=D0=B0=20=C2=AB=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82=D1=8C?= =?UTF-8?q?=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 4 ++-- package.json | 2 +- src/bridge/fileShare.ts | 12 ++++++++++++ src/bridge/panel.ts | 18 ++++++++++++++---- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc51b85..4a0800a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "telemax", - "version": "1.3.0-beta.5", + "version": "1.3.0-beta.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "telemax", - "version": "1.3.0-beta.5", + "version": "1.3.0-beta.6", "license": "AGPL-3.0-only", "dependencies": { "dotenv": "^17.2.3", diff --git a/package.json b/package.json index c7c0508..b77a0e0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "telemax", "private": true, "license": "AGPL-3.0-only", - "version": "1.3.0-beta.5", + "version": "1.3.0-beta.6", "type": "module", "scripts": { "dev": "tsx watch src/server/app.ts", diff --git a/src/bridge/fileShare.ts b/src/bridge/fileShare.ts index 7b93cbf..ac13727 100644 --- a/src/bridge/fileShare.ts +++ b/src/bridge/fileShare.ts @@ -123,6 +123,8 @@ export class FileShare { private chain: Promise = Promise.resolve(); private uploadHandler: ((t: UploadTicket, filePath: string, size: number, name: string) => Promise) | null = null; private busyTokens = new Set(); + /** Files being saved from MAX right now — the panel lists them before they're done. */ + private saving = new Map(); constructor( dataDir: string, @@ -200,6 +202,11 @@ export class FileShare { return this.index.tickets.find((t) => t.token === token); } + /** Files still being saved from MAX: name, declared size, bytes so far. */ + inProgress(): Array<{ name: string; size: number; written: number }> { + return [...this.saving.values()]; + } + pendingUploads(): number { return this.index.tickets.length; } @@ -286,9 +293,12 @@ export class FileShare { const part = path.join(dir, `${safe}.part`); let written = 0; const limit = Math.max(size, 0) + 1024 * 1024; // the declared size, a little slack + const progress = { name: safe, size, written: 0 }; + this.saving.set(id, progress); const guard = new Transform({ transform(chunk: Buffer, _enc, cb) { written += chunk.length; + progress.written = written; onProgress?.(written); if (written > limit) cb(new Error(`file grew past its declared ${size} bytes`)); else cb(null, chunk); @@ -300,6 +310,8 @@ export class FileShare { } catch (err) { await rm(dir, { recursive: true, force: true }); throw err; + } finally { + this.saving.delete(id); } return this.locked(() => this.addFile(id, safe, written, 'max2tg')); } diff --git a/src/bridge/panel.ts b/src/bridge/panel.ts index 1db6cc3..607aed7 100644 --- a/src/bridge/panel.ts +++ b/src/bridge/panel.ts @@ -161,21 +161,31 @@ function filesView(files: FileShare): View { return { text: '📁 Пересылка больших файлов отключена: FILES=off в .env.', markup: Markup.inlineKeyboard([back]).reply_markup }; } const list = files.list(); + const saving = files.inProgress(); const pending = files.pendingUploads(); - const pendingLine = pending ? `\n\n⏳ Ждут загрузки по ссылке: ${pending}.` : ''; const ttl = ttlText(files.opts.ttlMs); + const savingLines = saving.map((x) => `⏳ Сохраняется: ${x.name} — ${formatBytes(x.written)} из ${formatBytes(x.size)}`); + const pendingLine = pending ? `⏳ Ждут загрузки по ссылке: ${pending}` : ''; + const footer = + 'Здесь хранятся файлы, которые мост держит на сервере для скачивания по ссылке:\n' + + '• файлы из MAX больше 50 МБ — Telegram не принимает от ботов такие файлы напрямую;\n' + + '• загруженные для отправки в MAX, но не ушедшие из-за ошибки — их можно отправить ещё раз.\n\n' + + 'Файлы, которые уже ушли в MAX, на сервере не остаются.\n' + + `Файлы со ссылкой хранятся ${ttl} от последней выданной ссылки, потом удаляются.`; + const extra = [...savingLines, pendingLine].filter(Boolean).join('\n'); if (list.length === 0) { return { - text: `📁 Файлов нет.\n\nСюда попадают большие файлы, которые мост пересылает по ссылке: из MAX — больше 50 МБ, в MAX — больше 4 ГБ. Файл живёт ${ttl} от последней выданной ссылки, потом удаляется.${pendingLine}`, - markup: Markup.inlineKeyboard([back]).reply_markup, + text: `📁 Файлов нет.${extra ? `\n\n${extra}` : ''}\n\n${footer}`, + markup: Markup.inlineKeyboard([[Markup.button.callback('🔄 Обновить', 'tlmx_panel:files')], back]).reply_markup, }; } const total = list.reduce((s, f) => s + f.size, 0); const lines = list.map((f, i) => `${i + 1}. ${f.direction === 'max2tg' ? '⬇️' : '⬆️'} ${f.name} — ${formatBytes(f.size)}, до ${formatExpiry(f.expiresAt)}`); return { - text: `📁 Файлы на сервере: ${list.length}, всего ${formatBytes(total)}.\n⬇️ — из MAX, ⬆️ — для MAX. Ссылка живёт ${ttl}, потом файл удаляется.\n\n${lines.join('\n')}${pendingLine}`, + text: `📁 Файлы на сервере: ${list.length}, всего ${formatBytes(total)}.\n⬇️ — из MAX, ⬆️ — не ушли в MAX.\n\n${lines.join('\n')}${extra ? `\n\n${extra}` : ''}\n\n${footer}`, markup: Markup.inlineKeyboard([ ...list.slice(0, 20).map((f, i) => [Markup.button.callback(`${i + 1}. ${truncateUtf16(f.name, 48)}`, `tlmx_panel:file:${f.id}`)]), + [Markup.button.callback('🔄 Обновить', 'tlmx_panel:files')], back, ]).reply_markup, };