tglfs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +216 -0
  2. package/dist/auth.d.ts +58 -0
  3. package/dist/auth.js +187 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +627 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/crypto.d.ts +8 -0
  9. package/dist/crypto.js +45 -0
  10. package/dist/crypto.js.map +1 -0
  11. package/dist/download.d.ts +26 -0
  12. package/dist/download.js +228 -0
  13. package/dist/download.js.map +1 -0
  14. package/dist/errors.d.ts +18 -0
  15. package/dist/errors.js +35 -0
  16. package/dist/errors.js.map +1 -0
  17. package/dist/file-ops.d.ts +71 -0
  18. package/dist/file-ops.js +234 -0
  19. package/dist/file-ops.js.map +1 -0
  20. package/dist/gramjs.d.ts +14 -0
  21. package/dist/gramjs.js +70 -0
  22. package/dist/gramjs.js.map +1 -0
  23. package/dist/interactive.d.ts +11 -0
  24. package/dist/interactive.js +81 -0
  25. package/dist/interactive.js.map +1 -0
  26. package/dist/json.d.ts +2 -0
  27. package/dist/json.js +4 -0
  28. package/dist/json.js.map +1 -0
  29. package/dist/progress.d.ts +16 -0
  30. package/dist/progress.js +62 -0
  31. package/dist/progress.js.map +1 -0
  32. package/dist/protocol.d.ts +5 -0
  33. package/dist/protocol.js +27 -0
  34. package/dist/protocol.js.map +1 -0
  35. package/dist/search.d.ts +23 -0
  36. package/dist/search.js +81 -0
  37. package/dist/search.js.map +1 -0
  38. package/dist/secrets.d.ts +12 -0
  39. package/dist/secrets.js +29 -0
  40. package/dist/secrets.js.map +1 -0
  41. package/dist/shared/archive.d.ts +9 -0
  42. package/dist/shared/archive.js +95 -0
  43. package/dist/shared/archive.js.map +1 -0
  44. package/dist/shared/constants.d.ts +5 -0
  45. package/dist/shared/constants.js +6 -0
  46. package/dist/shared/constants.js.map +1 -0
  47. package/dist/shared/file-cards.d.ts +31 -0
  48. package/dist/shared/file-cards.js +103 -0
  49. package/dist/shared/file-cards.js.map +1 -0
  50. package/dist/shared/telegram-files.d.ts +84 -0
  51. package/dist/shared/telegram-files.js +137 -0
  52. package/dist/shared/telegram-files.js.map +1 -0
  53. package/dist/shared/upload.d.ts +44 -0
  54. package/dist/shared/upload.js +181 -0
  55. package/dist/shared/upload.js.map +1 -0
  56. package/dist/store.d.ts +15 -0
  57. package/dist/store.js +97 -0
  58. package/dist/store.js.map +1 -0
  59. package/dist/types.d.ts +18 -0
  60. package/dist/types.js +2 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/ufid.d.ts +8 -0
  63. package/dist/ufid.js +65 -0
  64. package/dist/ufid.js.map +1 -0
  65. package/dist/upload.d.ts +19 -0
  66. package/dist/upload.js +101 -0
  67. package/dist/upload.js.map +1 -0
  68. package/man/tglfs-delete.1 +19 -0
  69. package/man/tglfs-download.1 +42 -0
  70. package/man/tglfs-inspect.1 +34 -0
  71. package/man/tglfs-login.1 +53 -0
  72. package/man/tglfs-logout.1 +17 -0
  73. package/man/tglfs-receive.1 +26 -0
  74. package/man/tglfs-rename.1 +8 -0
  75. package/man/tglfs-search.1 +37 -0
  76. package/man/tglfs-send.1 +22 -0
  77. package/man/tglfs-status.1 +12 -0
  78. package/man/tglfs-unsend.1 +37 -0
  79. package/man/tglfs-upload.1 +28 -0
  80. package/man/tglfs.1 +70 -0
  81. package/package.json +66 -0
@@ -0,0 +1,44 @@
1
+ import type { FileCardRecord } from "./file-cards.js";
2
+ type UploadApiLike = {
3
+ messages: {
4
+ EditMessage: new (args: any) => any;
5
+ };
6
+ upload: {
7
+ SaveBigFilePart: new (args: any) => any;
8
+ };
9
+ InputFileBig: new (args: any) => any;
10
+ };
11
+ type UploadClient = {
12
+ sendMessage(peer: string, options: {
13
+ message: string;
14
+ }): Promise<any>;
15
+ sendFile(peer: string, options: {
16
+ file: any;
17
+ }): Promise<any>;
18
+ invoke(request: any): Promise<any>;
19
+ getMessages(peer: string, options: unknown): Promise<any[]>;
20
+ };
21
+ export type UploadSource = {
22
+ name: string;
23
+ size: number;
24
+ stream(): ReadableStream<Uint8Array> | Promise<ReadableStream<Uint8Array>>;
25
+ };
26
+ export type UploadProgress = {
27
+ bytesProcessed: number;
28
+ totalBytes: number;
29
+ };
30
+ export type UploadCurrentFormatOptions = {
31
+ Api: UploadApiLike;
32
+ peer?: string;
33
+ chunkSize: number;
34
+ password: string;
35
+ source: UploadSource;
36
+ onUfidProgress?: (progress: UploadProgress) => void;
37
+ onUploadProgress?: (progress: UploadProgress) => void;
38
+ };
39
+ export declare class DuplicateUfidError extends Error {
40
+ readonly ufid: string;
41
+ constructor(ufid: string);
42
+ }
43
+ export declare function uploadCurrentFormatSource(client: UploadClient, options: UploadCurrentFormatOptions): Promise<FileCardRecord>;
44
+ export {};
@@ -0,0 +1,181 @@
1
+ import { Buffer } from "buffer";
2
+ import { deriveAESKeyFromPassword, ENCRYPTION_CHUNK_SIZE, incrementCounter64By } from "../crypto.js";
3
+ import { computeUfidFromStream } from "../ufid.js";
4
+ import { UPLOAD_PART_SIZE } from "./constants.js";
5
+ import { serializeFileCardMessage } from "./file-cards.js";
6
+ import { lookupFileCardByUfid } from "./telegram-files.js";
7
+ export class DuplicateUfidError extends Error {
8
+ ufid;
9
+ constructor(ufid) {
10
+ super(`A file with UFID ${ufid} already exists.`);
11
+ this.name = "DuplicateUfidError";
12
+ this.ufid = ufid;
13
+ }
14
+ }
15
+ function createUploadFileId() {
16
+ const random = globalThis.crypto.getRandomValues(new Uint32Array(2));
17
+ return (BigInt(random[0] & 0x001fffff) << 32n) | BigInt(random[1]);
18
+ }
19
+ function createIvBytes(salt, counter) {
20
+ const bytes = new Uint8Array(salt.length + counter.length);
21
+ bytes.set(salt, 0);
22
+ bytes.set(counter, salt.length);
23
+ return bytes;
24
+ }
25
+ function resolvePeer(peer) {
26
+ return peer?.trim() ? peer.trim() : "me";
27
+ }
28
+ export async function uploadCurrentFormatSource(client, options) {
29
+ if (options.chunkSize < UPLOAD_PART_SIZE) {
30
+ throw new Error(`chunkSize (${options.chunkSize}) must be at least UPLOAD_PART_SIZE (${UPLOAD_PART_SIZE}).`);
31
+ }
32
+ const peer = resolvePeer(options.peer);
33
+ const totalBytes = options.source.size;
34
+ const ufid = await computeUfidFromStream(await options.source.stream(), (bytesProcessed, streamTotalBytes) => options.onUfidProgress?.({ bytesProcessed, totalBytes: streamTotalBytes }), totalBytes);
35
+ const duplicate = await lookupFileCardByUfid(client, ufid, { peer });
36
+ if (duplicate) {
37
+ throw new DuplicateUfidError(ufid);
38
+ }
39
+ const salt = globalThis.crypto.getRandomValues(new Uint8Array(16));
40
+ const initialCounter = globalThis.crypto.getRandomValues(new Uint8Array(16));
41
+ const aesKey = await deriveAESKeyFromPassword(options.password, salt);
42
+ let encryptionCounter = new Uint8Array(initialCounter);
43
+ let fileCardData = {
44
+ name: options.source.name,
45
+ ufid,
46
+ size: totalBytes,
47
+ uploadComplete: false,
48
+ chunks: [],
49
+ IV: Buffer.from(createIvBytes(salt, initialCounter)).toString("base64"),
50
+ };
51
+ const fileCardMessage = await client.sendMessage(peer, {
52
+ message: serializeFileCardMessage(fileCardData),
53
+ });
54
+ let uploadBytesProcessed = 0;
55
+ const byteCounterStream = new TransformStream({
56
+ transform(chunk, controller) {
57
+ uploadBytesProcessed += chunk.length;
58
+ options.onUploadProgress?.({ bytesProcessed: uploadBytesProcessed, totalBytes });
59
+ controller.enqueue(chunk);
60
+ },
61
+ });
62
+ const sourceStream = await options.source.stream();
63
+ const reader = sourceStream.pipeThrough(byteCounterStream).pipeThrough(new CompressionStream("gzip")).getReader();
64
+ let chunkIndex = 0;
65
+ let chunkBytesWritten = 0;
66
+ let chunkFileId = createUploadFileId();
67
+ let nextPartIndex = 0;
68
+ const partBuffer = new Uint8Array(UPLOAD_PART_SIZE);
69
+ let partBufferLength = 0;
70
+ const encryptionBuffer = new Uint8Array(ENCRYPTION_CHUNK_SIZE);
71
+ let encryptionBufferLength = 0;
72
+ const uploadPart = async (fileTotalParts) => {
73
+ const bytes = partBuffer.subarray(0, partBufferLength);
74
+ const result = await client.invoke(new options.Api.upload.SaveBigFilePart({
75
+ fileId: chunkFileId,
76
+ filePart: nextPartIndex,
77
+ fileTotalParts,
78
+ bytes: Buffer.from(bytes),
79
+ }));
80
+ if (!result) {
81
+ throw new Error(`Failed to upload chunk ${chunkIndex + 1} part ${nextPartIndex + 1}.`);
82
+ }
83
+ nextPartIndex += 1;
84
+ partBufferLength = 0;
85
+ };
86
+ const finalizeChunk = async (uploadComplete) => {
87
+ if (nextPartIndex === 0) {
88
+ return;
89
+ }
90
+ const uploadedChunk = new options.Api.InputFileBig({
91
+ id: chunkFileId,
92
+ parts: nextPartIndex,
93
+ name: `${ufid}.chunk${chunkIndex + 1}`,
94
+ });
95
+ const chunkMessage = await client.sendFile(peer, { file: uploadedChunk });
96
+ fileCardData = {
97
+ ...fileCardData,
98
+ uploadComplete,
99
+ chunks: [...fileCardData.chunks, chunkMessage.id],
100
+ };
101
+ await client.invoke(new options.Api.messages.EditMessage({
102
+ peer: fileCardMessage.peerId,
103
+ id: fileCardMessage.id,
104
+ message: serializeFileCardMessage(fileCardData),
105
+ }));
106
+ chunkIndex += 1;
107
+ chunkBytesWritten = 0;
108
+ chunkFileId = createUploadFileId();
109
+ nextPartIndex = 0;
110
+ };
111
+ const appendEncryptedBytes = async (bytes) => {
112
+ let offset = 0;
113
+ while (offset < bytes.length) {
114
+ const remainingChunkSpace = options.chunkSize - chunkBytesWritten;
115
+ const bytesToCopy = Math.min(bytes.length - offset, UPLOAD_PART_SIZE - partBufferLength, remainingChunkSpace);
116
+ partBuffer.set(bytes.subarray(offset, offset + bytesToCopy), partBufferLength);
117
+ offset += bytesToCopy;
118
+ partBufferLength += bytesToCopy;
119
+ chunkBytesWritten += bytesToCopy;
120
+ const partFull = partBufferLength === UPLOAD_PART_SIZE;
121
+ const chunkFull = chunkBytesWritten === options.chunkSize;
122
+ if (chunkFull) {
123
+ await uploadPart(nextPartIndex + 1);
124
+ await finalizeChunk(false);
125
+ continue;
126
+ }
127
+ if (partFull) {
128
+ await uploadPart(-1);
129
+ }
130
+ }
131
+ };
132
+ while (true) {
133
+ const { done, value } = await reader.read();
134
+ if (done) {
135
+ break;
136
+ }
137
+ if (!value || value.length === 0) {
138
+ continue;
139
+ }
140
+ let valueOffset = 0;
141
+ while (valueOffset < value.length) {
142
+ const bytesToCopy = Math.min(value.length - valueOffset, encryptionBuffer.length - encryptionBufferLength);
143
+ encryptionBuffer.set(value.subarray(valueOffset, valueOffset + bytesToCopy), encryptionBufferLength);
144
+ encryptionBufferLength += bytesToCopy;
145
+ valueOffset += bytesToCopy;
146
+ if (encryptionBufferLength === encryptionBuffer.length) {
147
+ const encrypted = new Uint8Array(await globalThis.crypto.subtle.encrypt({ name: "AES-CTR", counter: encryptionCounter, length: 64 }, aesKey, encryptionBuffer));
148
+ encryptionCounter = incrementCounter64By(encryptionCounter, Math.ceil(encryptionBuffer.length / 16));
149
+ encryptionBufferLength = 0;
150
+ await appendEncryptedBytes(encrypted);
151
+ }
152
+ }
153
+ }
154
+ if (encryptionBufferLength > 0) {
155
+ const encryptedTail = new Uint8Array(await globalThis.crypto.subtle.encrypt({ name: "AES-CTR", counter: encryptionCounter, length: 64 }, aesKey, encryptionBuffer.subarray(0, encryptionBufferLength)));
156
+ encryptionCounter = incrementCounter64By(encryptionCounter, Math.ceil(encryptionBufferLength / 16));
157
+ encryptionBufferLength = 0;
158
+ await appendEncryptedBytes(encryptedTail);
159
+ }
160
+ if (partBufferLength > 0) {
161
+ await uploadPart(nextPartIndex + 1);
162
+ }
163
+ await finalizeChunk(true);
164
+ if (!fileCardData.uploadComplete) {
165
+ fileCardData = {
166
+ ...fileCardData,
167
+ uploadComplete: true,
168
+ };
169
+ await client.invoke(new options.Api.messages.EditMessage({
170
+ peer: fileCardMessage.peerId,
171
+ id: fileCardMessage.id,
172
+ message: serializeFileCardMessage(fileCardData),
173
+ }));
174
+ }
175
+ return {
176
+ msgId: fileCardMessage.id,
177
+ date: fileCardMessage.date,
178
+ data: fileCardData,
179
+ };
180
+ }
181
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/shared/upload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { PersistedConfig } from "./types.js";
2
+ export declare const storePaths: {
3
+ configDir: string;
4
+ dataDir: string;
5
+ configFile: string;
6
+ sessionFile: string;
7
+ };
8
+ export declare function loadConfig(): Promise<PersistedConfig | null>;
9
+ export declare function saveConfig(config: PersistedConfig): Promise<void>;
10
+ export declare function deleteConfig(): Promise<void>;
11
+ export declare function loadSessionString(): Promise<string | null>;
12
+ export declare function saveSessionString(session: string): Promise<void>;
13
+ export declare function deleteSessionString(): Promise<void>;
14
+ export declare function clearPersistedState(removeConfig?: boolean): Promise<void>;
15
+ export declare function sessionExists(): Promise<boolean>;
package/dist/store.js ADDED
@@ -0,0 +1,97 @@
1
+ import envPaths from "env-paths";
2
+ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { CliError, EXIT_CODES } from "./errors.js";
5
+ import { BUNDLED_CHUNK_SIZE } from "./shared/constants.js";
6
+ const appPaths = envPaths("tglfs", { suffix: "" });
7
+ export const storePaths = {
8
+ configDir: appPaths.config,
9
+ dataDir: appPaths.data,
10
+ configFile: join(appPaths.config, "config.json"),
11
+ sessionFile: join(appPaths.data, "session.txt"),
12
+ };
13
+ function validateConfig(value) {
14
+ if (!value || typeof value !== "object") {
15
+ throw new CliError("invalid_config", "Stored config is invalid.", EXIT_CODES.MISSING_AUTH);
16
+ }
17
+ const record = value;
18
+ if (typeof record.apiId !== "number" ||
19
+ !Number.isFinite(record.apiId) ||
20
+ typeof record.apiHash !== "string" ||
21
+ record.apiHash.trim() === "" ||
22
+ (record.chunkSize !== undefined &&
23
+ (typeof record.chunkSize !== "number" || !Number.isFinite(record.chunkSize) || record.chunkSize <= 0)) ||
24
+ typeof record.phone !== "string" ||
25
+ record.phone.trim() === "") {
26
+ throw new CliError("invalid_config", "Stored config is invalid.", EXIT_CODES.MISSING_AUTH);
27
+ }
28
+ return {
29
+ apiId: record.apiId,
30
+ apiHash: record.apiHash,
31
+ chunkSize: record.chunkSize ?? BUNDLED_CHUNK_SIZE,
32
+ phone: record.phone,
33
+ };
34
+ }
35
+ async function ensureParentDir(filePath) {
36
+ await mkdir(dirname(filePath), { recursive: true });
37
+ }
38
+ async function writePrivateFile(filePath, content) {
39
+ await ensureParentDir(filePath);
40
+ await writeFile(filePath, content, { encoding: "utf8", mode: 0o600 });
41
+ }
42
+ export async function loadConfig() {
43
+ try {
44
+ const raw = await readFile(storePaths.configFile, "utf8");
45
+ return validateConfig(JSON.parse(raw));
46
+ }
47
+ catch (error) {
48
+ if (error?.code === "ENOENT") {
49
+ return null;
50
+ }
51
+ throw error;
52
+ }
53
+ }
54
+ export async function saveConfig(config) {
55
+ await writePrivateFile(storePaths.configFile, JSON.stringify(config, null, 2) + "\n");
56
+ }
57
+ export async function deleteConfig() {
58
+ await rm(storePaths.configFile, { force: true });
59
+ }
60
+ export async function loadSessionString() {
61
+ try {
62
+ const raw = await readFile(storePaths.sessionFile, "utf8");
63
+ const session = raw.trim();
64
+ return session === "" ? null : session;
65
+ }
66
+ catch (error) {
67
+ if (error?.code === "ENOENT") {
68
+ return null;
69
+ }
70
+ throw error;
71
+ }
72
+ }
73
+ export async function saveSessionString(session) {
74
+ await writePrivateFile(storePaths.sessionFile, session.trim() + "\n");
75
+ }
76
+ export async function deleteSessionString() {
77
+ await rm(storePaths.sessionFile, { force: true });
78
+ }
79
+ export async function clearPersistedState(removeConfig = false) {
80
+ await deleteSessionString();
81
+ if (removeConfig) {
82
+ await deleteConfig();
83
+ }
84
+ }
85
+ export async function sessionExists() {
86
+ try {
87
+ await stat(storePaths.sessionFile);
88
+ return true;
89
+ }
90
+ catch (error) {
91
+ if (error?.code === "ENOENT") {
92
+ return false;
93
+ }
94
+ throw error;
95
+ }
96
+ }
97
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export type PersistedConfig = {
2
+ apiId: number;
3
+ apiHash: string;
4
+ chunkSize: number;
5
+ phone: string;
6
+ };
7
+ export type { FileCardData, FileCardRecord } from "./shared/file-cards.js";
8
+ export type JsonEnvelope<T> = {
9
+ ok: true;
10
+ data: T;
11
+ } | {
12
+ ok: false;
13
+ error: {
14
+ code: string;
15
+ message: string;
16
+ details?: unknown;
17
+ };
18
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/dist/ufid.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export declare class UfidAccumulator {
2
+ private rolling;
3
+ private pending;
4
+ update(chunk: Uint8Array): Promise<void>;
5
+ digest(): Promise<string>;
6
+ }
7
+ export declare function computeUfidFromStream(stream: ReadableStream<Uint8Array>, onProgress?: (bytesProcessed: number, totalBytes: number) => void, totalBytes?: number): Promise<string>;
8
+ export declare function computeUfidFromBytes(bytes: Uint8Array): Promise<string>;
package/dist/ufid.js ADDED
@@ -0,0 +1,65 @@
1
+ const UFID_CHUNK_SIZE = 64 * 1024;
2
+ function toHex(bytes) {
3
+ return Array.from(bytes)
4
+ .map((byte) => byte.toString(16).padStart(2, "0"))
5
+ .join("");
6
+ }
7
+ async function digestChunk(rolling, chunk) {
8
+ const toHash = new Uint8Array(rolling.length + chunk.length);
9
+ toHash.set(rolling, 0);
10
+ toHash.set(chunk, rolling.length);
11
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", toHash.buffer);
12
+ return new Uint8Array(digest);
13
+ }
14
+ export class UfidAccumulator {
15
+ rolling = new Uint8Array(0);
16
+ pending = new Uint8Array(0);
17
+ async update(chunk) {
18
+ if (chunk.length === 0) {
19
+ return;
20
+ }
21
+ const combined = new Uint8Array(this.pending.length + chunk.length);
22
+ combined.set(this.pending, 0);
23
+ combined.set(chunk, this.pending.length);
24
+ let offset = 0;
25
+ while (offset + UFID_CHUNK_SIZE <= combined.length) {
26
+ const piece = combined.subarray(offset, offset + UFID_CHUNK_SIZE);
27
+ this.rolling = await digestChunk(this.rolling, piece);
28
+ offset += UFID_CHUNK_SIZE;
29
+ }
30
+ this.pending = combined.slice(offset);
31
+ }
32
+ async digest() {
33
+ if (this.pending.length > 0) {
34
+ const padded = new Uint8Array(UFID_CHUNK_SIZE);
35
+ padded.set(this.pending, 0);
36
+ this.rolling = await digestChunk(this.rolling, padded);
37
+ this.pending = new Uint8Array(0);
38
+ }
39
+ return toHex(this.rolling);
40
+ }
41
+ }
42
+ export async function computeUfidFromStream(stream, onProgress, totalBytes = 0) {
43
+ const reader = stream.getReader();
44
+ const accumulator = new UfidAccumulator();
45
+ let bytesProcessed = 0;
46
+ while (true) {
47
+ const { done, value } = await reader.read();
48
+ if (done) {
49
+ break;
50
+ }
51
+ if (!value || value.length === 0) {
52
+ continue;
53
+ }
54
+ bytesProcessed += value.length;
55
+ onProgress?.(bytesProcessed, totalBytes);
56
+ await accumulator.update(value);
57
+ }
58
+ return accumulator.digest();
59
+ }
60
+ export async function computeUfidFromBytes(bytes) {
61
+ const accumulator = new UfidAccumulator();
62
+ await accumulator.update(bytes);
63
+ return accumulator.digest();
64
+ }
65
+ //# sourceMappingURL=ufid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ufid.js","sourceRoot":"","sources":["../src/ufid.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { TelegramClient } from "telegram/client/TelegramClient.js";
2
+ import type { UploadProgress } from "./shared/upload.js";
3
+ export type UploadPathsResult = {
4
+ name: string;
5
+ ufid: string;
6
+ size: number;
7
+ msgId: number;
8
+ date: number;
9
+ chunks: number[];
10
+ sourcePaths: string[];
11
+ archived: boolean;
12
+ };
13
+ export declare function uploadPaths(client: TelegramClient, options: {
14
+ paths: string[];
15
+ chunkSize: number;
16
+ password: string;
17
+ onUfidProgress?: (progress: UploadProgress) => void;
18
+ onUploadProgress?: (progress: UploadProgress) => void;
19
+ }): Promise<UploadPathsResult>;
package/dist/upload.js ADDED
@@ -0,0 +1,101 @@
1
+ import { openAsBlob } from "node:fs";
2
+ import { stat } from "node:fs/promises";
3
+ import { basename, resolve } from "node:path";
4
+ import { CliError, EXIT_CODES } from "./errors.js";
5
+ import { getGramJs } from "./gramjs.js";
6
+ import { computeTarSize, createTarStream, defaultArchiveName } from "./shared/archive.js";
7
+ import { DuplicateUfidError, uploadCurrentFormatSource } from "./shared/upload.js";
8
+ async function resolveLocalArchiveEntry(inputPath) {
9
+ const absolutePath = resolve(inputPath);
10
+ let stats;
11
+ try {
12
+ stats = await stat(absolutePath);
13
+ }
14
+ catch (error) {
15
+ throw new CliError("invalid_input", `Upload path does not exist: ${absolutePath}.`, EXIT_CODES.GENERAL_ERROR, { path: absolutePath, cause: error instanceof Error ? error.message : String(error) });
16
+ }
17
+ if (!stats.isFile()) {
18
+ throw new CliError("invalid_input", `Upload path must be a file: ${absolutePath}.`, EXIT_CODES.GENERAL_ERROR, { path: absolutePath });
19
+ }
20
+ const blob = await openAsBlob(absolutePath);
21
+ return {
22
+ path: absolutePath,
23
+ name: basename(absolutePath),
24
+ size: stats.size,
25
+ lastModified: Math.floor(stats.mtimeMs),
26
+ stream() {
27
+ return blob.stream();
28
+ },
29
+ };
30
+ }
31
+ async function resolveUploadSource(paths) {
32
+ if (paths.length === 0) {
33
+ throw new CliError("invalid_input", "At least one file path is required.", EXIT_CODES.GENERAL_ERROR);
34
+ }
35
+ const entries = await Promise.all(paths.map((path) => resolveLocalArchiveEntry(path)));
36
+ if (entries.length === 1) {
37
+ const [entry] = entries;
38
+ return {
39
+ source: {
40
+ name: entry.name,
41
+ size: entry.size,
42
+ stream() {
43
+ return entry.stream();
44
+ },
45
+ },
46
+ sourcePaths: [entry.path],
47
+ archived: false,
48
+ };
49
+ }
50
+ return {
51
+ source: {
52
+ name: defaultArchiveName(),
53
+ size: computeTarSize(entries),
54
+ stream() {
55
+ return createTarStream(entries);
56
+ },
57
+ },
58
+ sourcePaths: entries.map((entry) => entry.path),
59
+ archived: true,
60
+ };
61
+ }
62
+ function normalizeUploadError(error) {
63
+ if (error instanceof CliError) {
64
+ return error;
65
+ }
66
+ if (error instanceof DuplicateUfidError) {
67
+ return new CliError("duplicate_ufid", `A file with UFID ${error.ufid} already exists in Saved Messages.`, EXIT_CODES.GENERAL_ERROR, { ufid: error.ufid });
68
+ }
69
+ if (error instanceof Error) {
70
+ return new CliError("upload_failed", error.message, EXIT_CODES.GENERAL_ERROR);
71
+ }
72
+ return new CliError("upload_failed", String(error), EXIT_CODES.GENERAL_ERROR);
73
+ }
74
+ export async function uploadPaths(client, options) {
75
+ const { Api } = getGramJs();
76
+ const resolved = await resolveUploadSource(options.paths);
77
+ try {
78
+ const record = await uploadCurrentFormatSource(client, {
79
+ Api,
80
+ chunkSize: options.chunkSize,
81
+ password: options.password,
82
+ source: resolved.source,
83
+ onUfidProgress: options.onUfidProgress,
84
+ onUploadProgress: options.onUploadProgress,
85
+ });
86
+ return {
87
+ name: record.data.name,
88
+ ufid: record.data.ufid,
89
+ size: record.data.size,
90
+ msgId: record.msgId,
91
+ date: record.date,
92
+ chunks: record.data.chunks,
93
+ sourcePaths: resolved.sourcePaths,
94
+ archived: resolved.archived,
95
+ };
96
+ }
97
+ catch (error) {
98
+ throw normalizeUploadError(error);
99
+ }
100
+ }
101
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ .TH TGLFS-DELETE 1 "March 2026" "tglfs 0.1.0" "User Commands"
2
+ .SH NAME
3
+ tglfs-delete \- delete one or more owned TGLFS files from Saved Messages
4
+ .SH SYNOPSIS
5
+ .B tglfs delete
6
+ [\fB--yes\fR] [\fB--json\fR] \fIufids...\fR
7
+ .SH DESCRIPTION
8
+ Deletes the Saved Messages file card plus all referenced chunk messages for each UFID.
9
+ .SH OPTIONS
10
+ .TP
11
+ .B --yes
12
+ Skip the confirmation prompt.
13
+ .TP
14
+ .B --json
15
+ Emit machine-readable JSON.
16
+ .SH AI AGENT NOTES
17
+ Agents should pass
18
+ .B --yes
19
+ in non-interactive sessions.
@@ -0,0 +1,42 @@
1
+ .TH TGLFS-DOWNLOAD 1 "March 2026" "tglfs 0.1.0" "User Commands"
2
+ .SH NAME
3
+ tglfs-download \- download a current-format or legacy TGLFS file by UFID
4
+ .SH SYNOPSIS
5
+ .B tglfs download
6
+ [\fIOPTIONS\fR] \fIufid\fR
7
+ .SH DESCRIPTION
8
+ Looks up a TGLFS file card in Telegram Saved Messages by UFID, downloads the encrypted chunks, decrypts and gunzips them, verifies the UFID, and writes the plaintext to disk.
9
+ .PP
10
+ Use
11
+ .B --legacy
12
+ for files that still require the older counter-advance behavior.
13
+ .SH OPTIONS
14
+ .TP
15
+ .B --output <path>
16
+ Destination path for the restored plaintext file.
17
+ .TP
18
+ .B --force
19
+ Overwrite an existing output file.
20
+ .TP
21
+ .B --legacy
22
+ Use the legacy decryption pipeline.
23
+ .TP
24
+ .B --password <password>
25
+ Decryption password.
26
+ .TP
27
+ .B --password-env [name]
28
+ Read the decryption password from an environment variable. If no name is provided, the command uses
29
+ .B TGLFS_DOWNLOAD_PASSWORD.
30
+ .TP
31
+ .B --password-stdin
32
+ Read the decryption password from standard input.
33
+ .TP
34
+ .B --json
35
+ Emit machine-readable JSON.
36
+ .SH ENVIRONMENT
37
+ .TP
38
+ .B TGLFS_DOWNLOAD_PASSWORD
39
+ Default environment variable for the decryption password.
40
+ .SH AI AGENT NOTES
41
+ If the download password is missing and the command is attached to a TTY, the CLI prompts for it. In a non-interactive session, the agent must provide the password via flags, environment variables, or standard input.
42
+ Interactive TTY runs also render a progress bar while the file is being restored. JSON mode stays silent until the final result so agents can parse the output safely.