mirror of
https://github.com/Trollobot/Telemax.git
synced 2026-10-19 10:23:24 +00:00
1.3.0-beta.9: «📤 Загрузить файл» в пульте — свой файлообменник (файл на сервере, ссылкой делиться где угодно, 📦 в списке); удаление и отмена пишутся в лог
This commit is contained in:
parent
0088e99807
commit
0d027bb493
8 changed files with 122 additions and 24 deletions
|
|
@ -127,9 +127,13 @@ async function showLink(res, t) {
|
|||
const free = Math.max(0, (await freeBytes(t.root)) - (body.reserveBytes ?? 0));
|
||||
const maxFile = typeof body.maxFileBytes === 'number' ? body.maxFileBytes : Infinity;
|
||||
const maxBytes = Math.min(free, maxFile);
|
||||
const wanted = body.name
|
||||
? `Ждём «${esc(body.name)}» (${esc(formatBytes(body.expectedSize ?? 0))}) — мост отправит его в MAX.`
|
||||
: 'Выберите файл — мост отправит его в MAX.';
|
||||
const store = body.purpose === 'store';
|
||||
const wanted = store
|
||||
? 'Выберите файл — он сохранится на сервере, а ссылку для скачивания бот пришлёт в Telegram.'
|
||||
: body.name
|
||||
? `Ждём «${esc(body.name)}» (${esc(formatBytes(body.expectedSize ?? 0))}) — мост отправит его в MAX.`
|
||||
: 'Выберите файл — мост отправит его в MAX.';
|
||||
const doneText = store ? 'Готово! Файл на сервере — ссылку на него бот пришлёт в Telegram. Страницу можно закрыть.' : 'Готово! Мост отправит файл в MAX — страницу можно закрыть.';
|
||||
const limits = [
|
||||
Number.isFinite(maxFile) ? `MAX принимает файлы до ${esc(formatBytes(maxFile))}.` : '',
|
||||
`Свободно на сервере: ${esc(formatBytes(free))}.`,
|
||||
|
|
@ -155,7 +159,7 @@ f.onchange=()=>{const x=f.files[0];s.textContent='';go.disabled=!x;
|
|||
go.onclick=()=>{const x=f.files[0];if(!x)return;go.disabled=true;f.disabled=true;stop.hidden=false;p.style.display='block';bar.style.width='0';
|
||||
r=new XMLHttpRequest();r.open('PUT',location.pathname.replace(/\\/$/,'')+'/upload');r.setRequestHeader('X-File-Name',encodeURIComponent(x.name));
|
||||
r.upload.onprogress=e=>{if(e.lengthComputable){bar.style.width=(e.loaded/e.total*100).toFixed(1)+'%';s.textContent=fmt(e.loaded)+' из '+fmt(e.total)}};
|
||||
r.onload=()=>{if(r.status===200){bar.style.width='100%';s.textContent='Готово! Мост отправит файл в MAX — страницу можно закрыть.';stop.hidden=true;f.disabled=true}else{s.textContent='Не получилось: '+(r.responseText||r.status);idle()}};
|
||||
r.onload=()=>{if(r.status===200){bar.style.width='100%';s.textContent=${JSON.stringify(doneText)};stop.hidden=true;f.disabled=true}else{s.textContent='Не получилось: '+(r.responseText||r.status);idle()}};
|
||||
r.onerror=()=>{s.textContent='Связь прервалась — попробуйте ещё раз.';idle()};
|
||||
r.onabort=()=>{s.textContent='Загрузка отменена. Можно выбрать файл заново.';p.style.display='none';idle()};
|
||||
r.send(x)};
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "telemax",
|
||||
"version": "1.3.0-beta.8",
|
||||
"version": "1.3.0-beta.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "telemax",
|
||||
"version": "1.3.0-beta.8",
|
||||
"version": "1.3.0-beta.9",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.2.3",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "telemax",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"version": "1.3.0-beta.8",
|
||||
"version": "1.3.0-beta.9",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/server/app.ts",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ export function formatExpiry(ms: number): string {
|
|||
}
|
||||
|
||||
type SentMessage = { message_id: number };
|
||||
type SendOpts = { message_thread_id: number; reply_parameters?: { message_id: number; allow_sending_without_reply: boolean } };
|
||||
/** No message_thread_id = the group's General topic (where the panel lives). */
|
||||
type SendOpts = { message_thread_id?: number; reply_parameters?: { message_id: number; allow_sending_without_reply: boolean } };
|
||||
|
||||
export interface BigFilesDeps {
|
||||
bot: Telegraf;
|
||||
|
|
@ -61,6 +62,8 @@ export interface BigFiles {
|
|||
offerUpload(p: { maxChatId: string; topicId: number; telegramMessageId: number; caption?: string; name?: string; size?: number }): Promise<void>;
|
||||
/** MAX → Telegram: a FILE over the bot upload limit — save it and post a download link. */
|
||||
relayFromMax(att: MaxAttachment, ctx: DownloadContext, opts: SendOpts): Promise<SentMessage>;
|
||||
/** The panel's «📤 Загрузить файл»: an upload link into the server's own store, posted in General. */
|
||||
offerStore(): Promise<void>;
|
||||
/** The panel's «📤 Отправить в MAX»: a stored file that didn't make it, sent again from disk. */
|
||||
resendToMax(id: string): Promise<{ ok: boolean; text: string }>;
|
||||
}
|
||||
|
|
@ -82,7 +85,7 @@ export function createBigFiles({ bot, max, targetGroupId, fileShare, onSentToMax
|
|||
logger.error('Editing a big-file notice failed — posting a new one', err);
|
||||
}
|
||||
}
|
||||
await send(text, { message_thread_id: topicId }, markup).catch((e) => logger.error('Posting a big-file notice failed', e));
|
||||
await send(text, topicId ? { message_thread_id: topicId } : {}, markup).catch((e) => logger.error('Posting a big-file notice failed', e));
|
||||
};
|
||||
const reserveText = formatBytes(fileShare.opts.reserveBytes);
|
||||
|
||||
|
|
@ -180,13 +183,70 @@ export function createBigFiles({ bot, max, targetGroupId, fileShare, onSentToMax
|
|||
bot.action(new RegExp(`^${CANCEL}(.+)$`), async (ctx) => {
|
||||
const ticket = fileShare.getTicket(ctx.match[1] ?? '');
|
||||
await ctx.answerCbQuery(ticket ? 'Загрузка отменена' : 'Ссылка уже не действует').catch(() => {});
|
||||
if (ticket) logger.info(`Upload link for «${ticket.name || 'any file'}» cancelled`);
|
||||
if (ticket) await fileShare.dropTicket(ticket.token);
|
||||
const text = (ctx.callbackQuery.message as { text?: string } | undefined)?.text;
|
||||
await ctx.editMessageText(ticket || !text ? '✖️ Загрузка отменена — ссылка больше не работает.' : text).catch(() => {});
|
||||
});
|
||||
|
||||
async function offerStore(): Promise<void> {
|
||||
if (!fileShare.enabled) {
|
||||
await send('⚠️ Пересылка больших файлов отключена в настройках (FILES=off).', {});
|
||||
return;
|
||||
}
|
||||
const room = await fileShare.roomFor(1);
|
||||
if (!room.fits) {
|
||||
await send(`⚠️ Загрузить файл не выйдет: на сервере свободно ${formatBytes(room.freeBytes)}, а ${reserveText} нужно оставить для обновлений.`, {});
|
||||
return;
|
||||
}
|
||||
const prompt = await send('⏳ Готовлю ссылку для загрузки файла на сервер…', {});
|
||||
const ticket = await fileShare.createUploadTicket({
|
||||
purpose: 'store',
|
||||
maxChatId: '',
|
||||
topicId: 0,
|
||||
telegramMessageId: prompt.message_id,
|
||||
name: '',
|
||||
expectedSize: 0,
|
||||
promptMessageId: prompt.message_id,
|
||||
});
|
||||
logger.info('Panel: offering an upload link into the server store');
|
||||
const svc = await fileShare.ensureService();
|
||||
if (!svc.ok) {
|
||||
report('service', svc.reason);
|
||||
await fileShare.dropTicket(ticket.token);
|
||||
await edit(prompt.message_id, 0, `⚠️ Ссылку для загрузки подготовить не удалось — ${svc.reason}.`);
|
||||
return;
|
||||
}
|
||||
await edit(
|
||||
prompt.message_id,
|
||||
0,
|
||||
`📤 Загрузите файл по ссылке — он сохранится на вашем сервере, а ссылку для скачивания я пришлю сюда же. Ею можно делиться где угодно.
|
||||
Ссылка на загрузку действует до ${formatExpiry(ticket.expiresAt)}.`,
|
||||
uploadKeyboard(`${svc.url}/f/${ticket.token}`, ticket.token),
|
||||
);
|
||||
}
|
||||
|
||||
fileShare.onUpload(async (ticket, filePath, size, name) => {
|
||||
const label = `«${name}» (${formatBytes(size)})`;
|
||||
if (ticket.purpose === 'store') {
|
||||
const file = await fileShare.adoptUpload(ticket, filePath, size, name);
|
||||
logger.info(`Panel store: ${name} (${size} bytes) uploaded`);
|
||||
const svc = await fileShare.ensureService(30_000);
|
||||
const until = formatExpiry(file.expiresAt);
|
||||
if (!svc.ok) {
|
||||
await edit(ticket.promptMessageId, 0, `✅ Файл ${label} на сервере до ${until}. Ссылку можно взять в пульте → 📁 Файлы.`);
|
||||
return;
|
||||
}
|
||||
await edit(
|
||||
ticket.promptMessageId,
|
||||
0,
|
||||
`✅ Файл ${label} на сервере до ${until}.
|
||||
🔗 ${svc.url}/f/${file.token}
|
||||
Ссылкой можно делиться — по ней откроется страница со скачиванием. Продлить или удалить — в пульте → 📁 Файлы.`,
|
||||
Markup.inlineKeyboard([Markup.button.url('⬇️ Скачать', `${svc.url}/f/${file.token}`)]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (size > MAX_FILE_LIMIT) {
|
||||
// The page and the service refuse this already — belt and braces.
|
||||
await fileShare.dropTicket(ticket.token);
|
||||
|
|
@ -240,7 +300,7 @@ export function createBigFiles({ bot, max, targetGroupId, fileShare, onSentToMax
|
|||
if (Date.now() - lastShown < 20_000) return;
|
||||
lastShown = Date.now();
|
||||
const pct = size > 0 ? ` (${Math.min(99, Math.floor((written / size) * 100))}%)` : '';
|
||||
void edit(msg.message_id, opts.message_thread_id, `${notice}
|
||||
void edit(msg.message_id, opts.message_thread_id ?? 0, `${notice}
|
||||
⏳ Сохранено ${formatBytes(written)} из ${formatBytes(size)}${pct}…`);
|
||||
};
|
||||
let file;
|
||||
|
|
@ -261,10 +321,10 @@ export function createBigFiles({ bot, max, targetGroupId, fileShare, onSentToMax
|
|||
}
|
||||
logger.error(`Saving the big MAX file ${name} failed`, err);
|
||||
report('save from MAX', err);
|
||||
await edit(msg.message_id, opts.message_thread_id, `📎 Файл ${label} — ${WHY_TG_UPLOAD}, а сохранить его для скачивания не удалось: ${humanError(err)}. Откройте его в MAX.`);
|
||||
await edit(msg.message_id, opts.message_thread_id ?? 0, `📎 Файл ${label} — ${WHY_TG_UPLOAD}, а сохранить его для скачивания не удалось: ${humanError(err)}. Откройте его в MAX.`);
|
||||
return msg;
|
||||
}
|
||||
await edit(msg.message_id, opts.message_thread_id, `${notice}
|
||||
await edit(msg.message_id, opts.message_thread_id ?? 0, `${notice}
|
||||
✅ Сохранён — готовлю ссылку…`);
|
||||
// Not awaited: the service may take a minute or two to come up, and this runs inside the
|
||||
// chat's queue — the next message must not wait for it.
|
||||
|
|
@ -272,12 +332,12 @@ export function createBigFiles({ bot, max, targetGroupId, fileShare, onSentToMax
|
|||
const svc = await fileShare.ensureService();
|
||||
if (!svc.ok) {
|
||||
report('service', svc.reason);
|
||||
await edit(msg.message_id, opts.message_thread_id, `📎 Файл ${label} — ${WHY_TG_UPLOAD}. Ссылку подготовить не удалось: ${svc.reason}.\nФайл сохранён до ${formatExpiry(file.expiresAt)} — новую ссылку можно получить в пульте → 📁 Файлы.`);
|
||||
await edit(msg.message_id, opts.message_thread_id ?? 0, `📎 Файл ${label} — ${WHY_TG_UPLOAD}. Ссылку подготовить не удалось: ${svc.reason}.\nФайл сохранён до ${formatExpiry(file.expiresAt)} — новую ссылку можно получить в пульте → 📁 Файлы.`);
|
||||
return;
|
||||
}
|
||||
await edit(
|
||||
msg.message_id,
|
||||
opts.message_thread_id,
|
||||
opts.message_thread_id ?? 0,
|
||||
`📎 Файл ${label} — ${WHY_TG_UPLOAD}, поэтому он лежит на вашем сервере. Скачайте по ссылке до ${formatExpiry(file.expiresAt)} — потом файл удалится.`,
|
||||
Markup.inlineKeyboard([Markup.button.url('⬇️ Скачать', `${svc.url}/f/${file.token}`)]),
|
||||
);
|
||||
|
|
@ -302,5 +362,5 @@ export function createBigFiles({ bot, max, targetGroupId, fileShare, onSentToMax
|
|||
}
|
||||
}
|
||||
|
||||
return { offerUpload, relayFromMax, resendToMax };
|
||||
return { offerUpload, offerStore, relayFromMax, resendToMax };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ export interface StoredFile {
|
|||
id: string;
|
||||
name: string;
|
||||
size: number;
|
||||
/** max2tg: saved from MAX for the topic; tg2max: uploaded for MAX (over MAX's limit). */
|
||||
direction: 'max2tg' | 'tg2max';
|
||||
/** max2tg: saved from MAX for the topic; tg2max: uploaded for MAX but not sent; stored: put on the server through the panel to share by link. */
|
||||
direction: 'max2tg' | 'tg2max' | 'stored';
|
||||
token: string;
|
||||
createdAt: number;
|
||||
expiresAt: number;
|
||||
|
|
@ -45,6 +45,8 @@ export interface StoredFile {
|
|||
}
|
||||
|
||||
export interface UploadTicket {
|
||||
/** 'store': the panel's «📤 Загрузить файл» — kept on the server for a link, not sent anywhere. */
|
||||
purpose?: 'max' | 'store';
|
||||
token: string;
|
||||
maxChatId: string;
|
||||
topicId: number;
|
||||
|
|
@ -226,11 +228,13 @@ export class FileShare {
|
|||
const ticket: UploadTicket = { ...t, token: newToken(), createdAt: now, expiresAt: now + this.opts.ttlMs };
|
||||
await this.writeToken(ticket.token, {
|
||||
kind: 'upload',
|
||||
purpose: ticket.purpose ?? 'max',
|
||||
name: ticket.name,
|
||||
expectedSize: ticket.expectedSize,
|
||||
expiresAt: ticket.expiresAt,
|
||||
reserveBytes: this.opts.reserveBytes,
|
||||
maxFileBytes: MAX_FILE_LIMIT,
|
||||
// The MAX limit only matters for a file going to MAX; the panel's store is bounded by the disk.
|
||||
...(ticket.purpose === 'store' ? {} : { maxFileBytes: MAX_FILE_LIMIT }),
|
||||
});
|
||||
this.index.tickets.push(ticket);
|
||||
await this.save();
|
||||
|
|
@ -263,6 +267,7 @@ export class FileShare {
|
|||
await mkdir(path.join(this.dir, 'items', id), { recursive: true });
|
||||
const target = path.join(this.dir, 'items', id, safeFileName(name));
|
||||
await rename(filePath, target);
|
||||
if (ticket.purpose === 'store') return this.addFile(id, safeFileName(name), size, 'stored');
|
||||
return this.addFile(id, safeFileName(name), size, 'tg2max', {
|
||||
maxChatId: ticket.maxChatId,
|
||||
topicId: ticket.topicId,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ export interface ControlPanelDeps {
|
|||
pause: PauseControl;
|
||||
/** Big files kept for download links — the «📁 Файлы» section. */
|
||||
files?: FileShare;
|
||||
/** «📤 Загрузить файл»: an upload link into the server store, posted in General (bigFiles.ts). */
|
||||
offerStore?: () => Promise<void>;
|
||||
/** «📤 Отправить в MAX» for a stored file that didn't make it (bigFiles.ts). */
|
||||
resendToMax?: (id: string) => Promise<{ ok: boolean; text: string }>;
|
||||
}
|
||||
|
|
@ -169,22 +171,25 @@ function filesView(files: FileShare): View {
|
|||
const footer =
|
||||
'Здесь хранятся файлы, которые мост держит на сервере для скачивания по ссылке:\n' +
|
||||
'• файлы из MAX больше 50 МБ — Telegram не принимает от ботов такие файлы напрямую;\n' +
|
||||
'• загруженные для отправки в MAX, но не ушедшие из-за ошибки — их можно отправить ещё раз.\n\n' +
|
||||
'• загруженные для отправки в MAX, но не ушедшие из-за ошибки — их можно отправить ещё раз;\n' +
|
||||
'• загруженные вами через «📤 Загрузить файл» — чтобы делиться ссылкой.\n\n' +
|
||||
'Файлы, которые уже ушли в MAX, на сервере не остаются.\n' +
|
||||
`Файлы со ссылкой хранятся ${ttl} от последней выданной ссылки, потом удаляются.`;
|
||||
const extra = [...savingLines, pendingLine].filter(Boolean).join('\n');
|
||||
if (list.length === 0) {
|
||||
return {
|
||||
text: `📁 Файлов нет.${extra ? `\n\n${extra}` : ''}\n\n${footer}`,
|
||||
markup: Markup.inlineKeyboard([[Markup.button.callback('🔄 Обновить', 'tlmx_panel:files')], back]).reply_markup,
|
||||
markup: Markup.inlineKeyboard([[Markup.button.callback('📤 Загрузить файл', 'tlmx_panel:fupload')], [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)}`);
|
||||
const icon = (f: StoredFile) => (f.direction === 'max2tg' ? '⬇️' : f.direction === 'stored' ? '📦' : '⬆️');
|
||||
const lines = list.map((f, i) => `${i + 1}. ${icon(f)} ${f.name} — ${formatBytes(f.size)}, до ${formatExpiry(f.expiresAt)}`);
|
||||
return {
|
||||
text: `📁 Файлы на сервере: ${list.length}, всего ${formatBytes(total)}.\n⬇️ — из MAX, ⬆️ — не ушли в MAX.\n\n${lines.join('\n')}${extra ? `\n\n${extra}` : ''}\n\n${footer}`,
|
||||
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:fupload')],
|
||||
[Markup.button.callback('🔄 Обновить', 'tlmx_panel:files')],
|
||||
back,
|
||||
]).reply_markup,
|
||||
|
|
@ -192,7 +197,7 @@ function filesView(files: FileShare): View {
|
|||
}
|
||||
|
||||
function fileCardView(f: StoredFile, files: FileShare, link?: string, canResend = false): View {
|
||||
const where = f.direction === 'max2tg' ? 'пришёл из MAX' : 'загружен для MAX';
|
||||
const where = f.direction === 'max2tg' ? 'пришёл из MAX' : f.direction === 'stored' ? 'загружен вами для ссылки' : 'загружен для MAX, но не ушёл';
|
||||
const linkLine = link ? `\n\n🔗 ${link}\nСсылку можно переслать — по ней откроется страница со скачиванием.` : '';
|
||||
return {
|
||||
text: `📄 ${f.name}\n${formatBytes(f.size)} · ${where}\nХранится до ${formatExpiry(f.expiresAt)}, потом удалится.${linkLine}`,
|
||||
|
|
@ -264,7 +269,7 @@ function pauseView(): View {
|
|||
}
|
||||
|
||||
export function wireControlPanel(deps: ControlPanelDeps): void {
|
||||
const { bot, targetGroupId, max, getActivePhone, triggerFullResync, leaves, startDialog, resolveContactName, getStatus, pause, files, resendToMax } = deps;
|
||||
const { bot, targetGroupId, max, getActivePhone, triggerFullResync, leaves, startDialog, resolveContactName, getStatus, pause, files, resendToMax, offerStore } = deps;
|
||||
const card = (f: StoredFile, link?: string): View => fileCardView(f, files!, link, Boolean(resendToMax));
|
||||
const root = (): View => rootView(getActivePhone(), pause.until());
|
||||
const system = (): View => systemView(pause.until() != null);
|
||||
|
|
@ -389,8 +394,14 @@ export function wireControlPanel(deps: ControlPanelDeps): void {
|
|||
const back = Markup.inlineKeyboard([[Markup.button.callback('◀️ К файлам', 'tlmx_panel:files')]]).reply_markup;
|
||||
await ctx.editMessageText(r.text, { reply_markup: back }).catch(() => {});
|
||||
});
|
||||
bot.action('tlmx_panel:fupload', async (ctx) => {
|
||||
await ctx.answerCbQuery('Готовлю ссылку для загрузки…').catch(() => {});
|
||||
if (offerStore) void offerStore().catch((err) => logger.error('Panel store upload failed', err));
|
||||
});
|
||||
bot.action(/^tlmx_panel:fdel:(.+)$/, async (ctx) => {
|
||||
const name = files?.get(ctx.match[1] ?? '')?.name;
|
||||
const removed = files ? await files.remove(ctx.match[1] ?? '') : false;
|
||||
if (removed) logger.info(`Panel: file «${name}» deleted`);
|
||||
await ctx.answerCbQuery(removed ? 'Файл удалён' : 'Файла уже нет').catch(() => {});
|
||||
if (files) await edit(ctx, filesView(files));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2813,6 +2813,7 @@ export function wireBridge({
|
|||
pause,
|
||||
files: fileShare,
|
||||
resendToMax: (id) => bigFiles.resendToMax(id),
|
||||
offerStore: () => bigFiles.offerStore(),
|
||||
// Warm-cache name lookup (buildRoster already fetched the profiles); undefined on a miss lets
|
||||
// the panel fall back to CONTACT_INFO.
|
||||
resolveContactName: (uid) => {
|
||||
|
|
|
|||
|
|
@ -205,3 +205,20 @@ describe('big-file errors', () => {
|
|||
expect(s).toContain('ENOENT');
|
||||
});
|
||||
});
|
||||
|
||||
describe('the panel store («📤 Загрузить файл»)', () => {
|
||||
it('keeps the upload as a 📦 file with a link, with no MAX limit and nowhere to send it', async () => {
|
||||
const share = new FileShare(dataDir, opts);
|
||||
await share.init();
|
||||
const ticket = await share.createUploadTicket({ purpose: 'store', maxChatId: '', topicId: 0, telegramMessageId: 1, name: '', expectedSize: 0 });
|
||||
const tokenBody = JSON.parse(await readFile(path.join(share.dir, 'tokens', `${ticket.token}.json`), 'utf8'));
|
||||
expect(tokenBody).toMatchObject({ kind: 'upload', purpose: 'store' });
|
||||
expect(tokenBody.maxFileBytes).toBeUndefined();
|
||||
const dir = path.join(share.dir, 'incoming', ticket.token);
|
||||
await mkdir(dir, { recursive: true });
|
||||
await writeFile(path.join(dir, 'photos.zip'), 'zz');
|
||||
const f = await share.adoptUpload(ticket, path.join(dir, 'photos.zip'), 2, 'photos.zip');
|
||||
expect(f.direction).toBe('stored');
|
||||
expect(f.target).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue