telstore 0.1.5 → 0.1.6

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 (3) hide show
  1. package/README.md +74 -175
  2. package/package.json +1 -1
  3. package/src/caption.js +2 -2
package/README.md CHANGED
@@ -1,78 +1,68 @@
1
1
  # telstore
2
2
 
3
- Split large files into 1.8GB chunks, store them on Telegram, and restore them intact.
3
+ Split large files into chunks, store them on Telegram, and restore them byte-for-byte.
4
+
5
+ telstore signs in with your own Telegram account over MTProto, not a bot: the Bot API caps
6
+ uploads at 50MB per file, a user account gets 2GB.
7
+
8
+ Needs Node.js 18+ and an `api_id`/`api_hash` from <https://my.telegram.org> → API
9
+ development tools; `login` asks for those, your phone number and the verification code.
4
10
 
5
11
  ## Quick start
6
12
 
7
13
  ```bash
8
- npx telstore login # once only
9
- npx telstore config chat @my_backups # where backups go, from now on
10
- npx telstore data.tar # split it and send it there
11
- npx telstore data.tar --to @somewhere # somewhere else, this run only
12
- npx telstore config # every setting and where its value comes from
13
- npx telstore status # account, destination, unfinished backups
14
- npx telstore list # what is already stored in the destination
14
+ npx telstore login # once only
15
+ npx telstore config chat @my_backups # where backups go, from now on
16
+ npx telstore data.tar # split it and send it there
17
+ npx telstore list # what is already in the destination
15
18
  npx telstore restore telstore-20260905-7f3a91
16
- npx telstore delete telstore-20260905-7f3a91 # take it back out of the chat, for good
17
- npx telstore token # a session token for a machine you do not trust
18
19
  ```
19
20
 
20
- ## What you need
21
-
22
- - Node.js 18 or newer.
23
- - An `api_id` and `api_hash` from <https://my.telegram.org> → API development tools. The `login` command asks for both, plus your phone number and the verification code.
21
+ ## Commands
24
22
 
25
- telstore signs in with your own Telegram account (MTProto), not a bot. That is a hard requirement: the Bot API caps uploads at 50MB per file, while a user account gets 2GB.
23
+ | Command | What it does |
24
+ |---|---|
25
+ | `telstore login` | Log in to Telegram. Add `--token` to log in with a session token instead. |
26
+ | `telstore <file>` | Split the file and upload it. Prints the `backupId` you restore with. |
27
+ | `telstore list` | The backups stored in the destination, newest first. |
28
+ | `telstore restore <backup-id>` | Download every chunk and reassemble the file. |
29
+ | `telstore delete <backup-id>` | Remove a backup's chunks and manifest from the chat, for good. |
30
+ | `telstore status` | Account, destination, and unfinished backups. |
31
+ | `telstore config` | Show or change settings. |
32
+ | `telstore token` | Print a session token for a machine you do not trust. |
33
+ | `telstore logout` | Remove the locally stored session. |
26
34
 
27
- ## Settings and flags
35
+ ## Settings
28
36
 
29
- There are two ways to say what telstore should do, and they never overlap. **`config` writes;
30
- flags do not.** A flag applies to the run you typed it on and changes nothing on disk, so
31
- `--to @elsewhere` sends one backup elsewhere without moving the destination for the next one.
37
+ `config` writes; flags do not. A flag applies to the run you typed it on and changes nothing
38
+ on disk, so `--to @elsewhere` sends one backup elsewhere without moving the destination for
39
+ the next one.
32
40
 
33
41
  ```bash
34
- npx telstore config # everything, and whether it is yours or a default
35
- npx telstore config chat # one value, bare, ready to pipe
36
- npx telstore config chunkSize 500MB # change it for good
37
- npx telstore config chunkSize --unset # back to the default
42
+ npx telstore config # every setting, and whether it is yours or a default
43
+ npx telstore config chunkSize 500MB # change it for good
44
+ npx telstore config chunkSize --unset # back to the default
38
45
  ```
39
46
 
40
47
  | Setting | Flag | Default | Meaning |
41
48
  |---|---|---|---|
42
- | `chat` | `--to` | none | `@username`, `-100123…`, or `me`. A negative channel id works with a space or an `=`, as a flag or as a config value. |
43
- | `chunkSize` | `--chunk-size` | `1800MB` | e.g. `1.8GB`, `500MB`. Hard ceiling 1950MB. An unfinished backup keeps the size it started with. |
44
- | `uploadConcurrency` | `--upload-concurrency` | `32` | 512KB parts sent in parallel while uploading. An integer from 1 to 64. |
45
- | `downloadConcurrency` | `--download-concurrency` | `8` | 8MB slices fetched in parallel while restoring. An integer from 1 to 64. |
46
- | `limit` | `--limit` | `20` | How many backups `list` shows, newest first |
47
- | `verbose` | `--verbose` | off | Show the Telegram client's own connection logs, hidden by default so they do not break up the progress bar |
48
-
49
- `--yes` has no setting either — it answers the confirmation `delete` asks before destroying
50
- a backup, and an answer stored in a file would be an answer to a question nobody heard.
51
-
52
- `--out <path>` has no setting: it names where one particular restore should write, and
53
- defaults to the basename in the manifest. Relative paths resolve against the current directory.
54
-
55
- `config` reads and writes only settings — the `api_id`, `api_hash` and session that `login`
56
- stores in the same file are not reachable from it. A value is checked before it is written,
57
- so `config chunkSize 9GB` is refused there and then rather than at the start of a long upload.
49
+ | `chat` | `--to` | none | `@username`, `-100123…`, or `me` |
50
+ | `chunkSize` | `--chunk-size` | `1800MB` | e.g. `1.8GB`, `500MB`. Ceiling 1950MB. |
51
+ | `uploadConcurrency` | `--upload-concurrency` | `32` | 512KB parts in parallel while uploading, 1–64 |
52
+ | `downloadConcurrency` | `--download-concurrency` | `8` | 8MB slices in parallel while restoring, 1–64 |
53
+ | `limit` | `--limit` | `20` | How many backups `list` shows |
54
+ | `verbose` | `--verbose` | off | Show the Telegram client's own connection logs |
55
+
56
+ Three flags have no setting behind them: `--out <path>` names where one restore writes,
57
+ `--yes` skips the confirmation `delete` asks, and `--token` takes no value — a token written
58
+ on the command line would sit in `ps` and in that machine's shell history, so it is pasted at
59
+ a prompt that does not echo.
58
60
 
59
61
  ## What the chat looks like
60
62
 
61
- Every chunk goes up as a document captioned `📦 <backupId> · 3/12`, and the manifest that
62
- follows carries a summary card:
63
-
64
- ```
65
- 🗄 data.tar
66
- 💾 21.4 GB · 12 chunks
67
- 🆔 telstore-20260905-7f3a91
68
- 📅 2026-09-05 16:40 UTC
69
-
70
- ↩ npx telstore restore telstore-20260905-7f3a91
71
- #telstore
72
- ```
73
-
74
- `npx telstore list` reads those cards straight out of the chat — one search, no downloads —
75
- and lays them out as a table:
63
+ Every chunk goes up as a document captioned `📦 <backupId> · 3/12`, followed by a manifest
64
+ carrying a summary card — file name, size, id, date and the restore command. `list` reads
65
+ those cards straight out of the chat, one search and no downloads:
76
66
 
77
67
  ```
78
68
  Destination https://web.telegram.org/k/#@my_backups
@@ -84,141 +74,50 @@ telstore-20260901-9de447 photos.zip 940.3 MB 1 2026-09-01
84
74
  2 backups. Restore with: npx telstore restore <backup-id>
85
75
  ```
86
76
 
87
- A backup uploaded before the card existed still gets a row, with dashes where the caption
88
- says nothing — `list` reports what the chat holds and never fills gaps with guesses.
89
-
90
- ## How it works
91
-
92
- Every run mints a `backupId`. The file is read directly by offset — no temporary copies — and uploaded as documents named `<backupId>.partNNNN`. Once every chunk is up, telstore sends a JSON manifest listing the message id and sha256 of each one. Restore needs only the `backupId`: it finds the manifest in the chat, downloads each chunk to its exact position in a `.partial` file, checks every chunk's sha256 and size, and renames it to the real file only after *all* chunks match.
93
-
94
- If the connection drops during an **upload**, just run the same command again — progress lives in `~/.telstore/state/` and finished chunks are skipped, keeping the same `backupId`. Two things to know about rerunning:
95
-
96
- - Running again against a destination that differs from the one in the unfinished progress makes telstore **refuse to run** rather than silently redirect — one backup cannot be split across two destinations. The error names the chat to pass as `--to` to carry on, or the state file to delete to start a new backup. It reads the same whether the mismatch came from a flag or from your configured `chat`.
97
- - Running again **without** `--chunk-size` resumes at the size the backup started with, whatever your configured `chunkSize` says today. A setting is what to use when nobody asks for anything; it is not somebody asking.
98
- - Running again **with** a `--chunk-size` that differs from that size makes telstore **refuse to run**: the chunks already in the chat were cut that way and cannot be re-cut. Drop the flag to carry on, or delete the state file to start a new backup — which leaves the chunks already sent in the chat with nothing pointing at them.
99
-
100
- `Ctrl-C` during an upload names the backup it was working on, so `telstore status` and a later `restore` have something to go on. telstore keeps the **20 most recent** unfinished backups in `~/.telstore/state/`; starting a new one past that drops the oldest record and says which id it dropped. Only the local record goes — the chunks that backup sent stay in the chat, searchable by that id, but it can no longer be resumed.
101
-
102
- **Restore keeps no state to resume from.** Pressing `Ctrl-C` mid-restore saves nothing — running again starts over.
103
-
104
- ## Deleting a backup
105
-
106
- ```bash
107
- npx telstore delete telstore-20260905-7f3a91
108
- ```
109
-
110
- It prints what it is about to destroy, asks once, and then removes every chunk message and
111
- the manifest from the chat and drops the local record if there is one. `--yes` skips the
112
- question. **There is no undo** — Telegram is the only copy.
113
-
114
- It also works on a backup that never finished: those have chunks in the chat but no manifest,
115
- so `list` cannot see them and only `status` knows they exist. `delete` reads the local record
116
- instead and clears both.
77
+ ## Resuming an upload
117
78
 
118
- The chunks go first and the manifest goes last, deliberately. The manifest is the only list
119
- of the message ids, so if a delete is interrupted — Ctrl-C, a dropped connection — running
120
- the same command again finds that list still there and finishes the job. Telegram says
121
- nothing about an id that is already gone, so a second run costs nothing. In between the two
122
- runs the backup still shows up in `list`, and a `restore` of it fails loudly rather than
123
- handing over a partial file.
79
+ Progress lives in `~/.telstore/state/` (the 20 most recent), so running the same command
80
+ again skips the finished chunks and keeps the same `backupId`. A resumed backup keeps the
81
+ chunk size it started with; passing a `--chunk-size` or a destination that differs from its
82
+ own makes telstore refuse to run rather than re-cut or redirect it silently.
124
83
 
125
- Two things it refuses rather than guesses at: a manifest whose body names a *different*
126
- backup (a file renamed in the chat — its message ids point at somebody else's chunks), and a
127
- manifest or local record giving a message id that is not a whole positive number. Neither
128
- deletes anything at all. A manifest too damaged for `restore` to use *can* still be deleted —
129
- that is usually the one you want gone.
84
+ **Restore keeps no state** — `Ctrl-C` mid-restore saves nothing, running again starts over.
85
+ And `delete` has **no undo**: Telegram is the only copy.
130
86
 
131
87
  ## Running on a machine you do not trust
132
88
 
133
- `login` leaves your Telegram session on the machine you run it on, in plain text. When that
134
- machine is not yours, print a **session token** on one that is and log in with that instead:
89
+ `login` leaves your session on disk in plain text. When the machine is not yours, print a
90
+ **session token** on one that is and log in with that instead:
135
91
 
136
92
  ```bash
137
- # on the machine you are already logged in on
138
- npx telstore token # asks for a passphrase, twice
139
- # prints one line: tls1.…
140
-
141
- # on the other machine
142
- npx telstore login --token # paste the token, then type the passphrase
143
- npx telstore restore telstore-20260905-7f3a91
144
- npx telstore logout # when you are done
93
+ npx telstore token # on your own machine: asks for a passphrase, prints one line
94
+ npx telstore login --token # on the other one: paste the token, then the passphrase
95
+ npx telstore logout # when you are done
145
96
  ```
146
97
 
147
98
  The token holds your `api_id`, `api_hash`, session and settings, encrypted with the
148
- passphrase (AES-256-GCM, key derived with scrypt). It is one line of base64url, so it
149
- survives a chat message, an email or a QR code — and a copy that leaks without the
150
- passphrase is not enough to use your account.
151
-
152
- `login --token` stores the token exactly as it arrived, so **the session never exists on that
153
- machine in a form anyone can read**. Every command that talks to Telegram asks for the
154
- passphrase and opens it in memory:
155
-
156
- ```json
157
- { "sealed": "tls1.…", "settings": { "chat": "@my_backups" } }
158
- ```
159
-
160
- `npx telstore status` shows which of the two you are running:
161
-
162
- ```
163
- Session /home/you/.telstore/config.json (sealed — opened with a passphrase)
164
- Account Sho (@shovity)
165
- Destination https://web.telegram.org/k/#@my_backups
166
- Unfinished none
167
- ```
99
+ passphrase (AES-256-GCM, scrypt), as one line of base64url that survives a chat message or a
100
+ QR code. `login --token` stores it exactly as it arrived and every command asks for the
101
+ passphrase, so the session never exists on that machine in readable form.
168
102
 
169
- ### What is left behind
170
-
171
- - **No readable session.** `logout` removes the sealed blob, and with it the `api_hash`,
172
- which lives inside it.
173
- - **State files, yes.** An upload writes `~/.telstore/state/<key>.json` so it can be resumed
174
- after an interruption. It holds the file path, the chunk sizes and the message ids — no
175
- secret — and losing it would strand chunks in the chat with nothing able to name them.
176
-
177
- ### Things worth knowing before you use one
178
-
179
- - **`--token` takes no value, on purpose.** A token written on the command line would sit in
180
- `ps` for the whole life of the command and stay in that machine's shell history afterwards.
181
- It is pasted at a prompt that does not echo it.
182
- - **The passphrase may be left empty**, and then the token says so on its face: it starts
183
- `tls0.` instead of `tls1.`, `token` warns, and `login` stores the session in plain text
184
- exactly as an ordinary login would. telstore will not write a file that looks encrypted and
185
- is not.
186
- - **telstore cannot take a token back.** There is no expiry and no revocation. Like `logout`,
187
- it says nothing to Telegram — to end the session for good, open Telegram → Settings →
188
- Devices (Active sessions) and terminate it. Every copy of the token dies with it.
189
- - **A wrong passphrase and a damaged token are the same event** to AES-GCM: one failed
190
- authentication check. telstore names both possibilities rather than guessing which it was.
103
+ There is no expiry and no revocation: to end a session for good, terminate it under Telegram
104
+ → Settings → Devices. An empty passphrase is allowed, and then the token says so on its face
105
+ — it starts `tls0.` and `login` stores the session in plain text.
191
106
 
192
107
  ## Limits worth knowing
193
108
 
194
- - A chunk cannot exceed 1950MB: Telegram accepts at most 4000 parts of 512KB per file, an arithmetic ceiling of about 1953MB, and telstore stops at 1950MB to leave a safety margin.
195
- - The data is **not** encrypted. Don't upload anything you would mind sitting on someone else's infrastructure. The one thing telstore does encrypt is a session token, and that protects your login rather than your files.
196
- - Deleting a chunk message on Telegram destroys the backup, with no way to recover it. Use `npx telstore delete <backup-id>` when that is what you actually want.
197
- - Keep the `backupId`. Without it you have to hunt for the manifest in the chat by hand.
198
-
199
- ## Where the config lives
200
-
201
- `~/.telstore/config.json` (mode 600) holds `apiId`, `apiHash` and the session at the top level, with everything `config` manages under `settings`:
202
-
203
- ```json
204
- {
205
- "apiId": 123456,
206
- "apiHash": "…",
207
- "session": "…",
208
- "settings": { "chat": "@my_backups", "chunkSize": 524288000 }
209
- }
210
- ```
211
-
212
- A machine logged in with `npx telstore login --token` holds `sealed` instead of those three
213
- fields, and nothing else about the account:
214
-
215
- ```json
216
- { "sealed": "tls1.…", "settings": { "chat": "@my_backups" } }
217
- ```
109
+ - **Your data is not encrypted.** Don't upload anything you would mind sitting on someone
110
+ else's infrastructure — the one thing telstore encrypts is a session token, and that
111
+ protects your login rather than your files.
112
+ - A chunk cannot exceed 1950MB: Telegram accepts at most 4000 parts of 512KB per file.
113
+ - Deleting a chunk message in the Telegram app destroys the backup, and keeping the
114
+ `backupId` is what saves you hunting for its manifest in the chat by hand.
218
115
 
219
- A file holding both shapes is refused rather than guessed at — there would be no way to know
220
- which account was meant.
116
+ Settings and credentials live in `~/.telstore/config.json`, mode 600 — `apiId`, `apiHash` and
117
+ the session at the top level (or a single `sealed` blob after `login --token`), everything
118
+ `config` manages under `settings`. Editing it by hand is fine: a value that cannot be used is
119
+ named on the next run, with the file and the key.
221
120
 
222
- Editing it by hand is fine, and a value that cannot be used is named on the next run — with the file and the key, never with a flag you did not type.
121
+ ## License
223
122
 
224
- `npx telstore logout` **only deletes the locally stored session** and keeps the rest — the session is still alive on Telegram's side. To revoke access for good, open Telegram → Settings → Devices (Active sessions) and terminate that session.
123
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "telstore",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Split large files into chunks and store them on Telegram",
5
5
  "keywords": [
6
6
  "telegram",
package/src/caption.js CHANGED
@@ -24,7 +24,7 @@ export function chunkCaption({ id, number, total }) {
24
24
 
25
25
  export function manifestCaption({ id, name, size, chunks, createdAt }) {
26
26
  return [
27
- `🗄 ${oneLine(name)}`,
27
+ `📄 ${oneLine(name)}`,
28
28
  `💾 ${formatBytes(size)} · ${chunks} chunk${chunks === 1 ? '' : 's'}`,
29
29
  `🆔 ${id}`,
30
30
  `📅 ${utcMinutes(createdAt)}`,
@@ -45,7 +45,7 @@ function marker(lines, emoji) {
45
45
  export function parseManifestCaption(text) {
46
46
  const lines = String(text ?? '').split('\n')
47
47
 
48
- const name = marker(lines, '🗄')
48
+ const name = marker(lines, '📄')
49
49
  const totals = marker(lines, '💾')
50
50
  const id = marker(lines, '🆔')
51
51
  const createdAt = marker(lines, '📅')