tgbox 0.0.0-stage → 1.0.1

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.
package/README.md CHANGED
@@ -1,3 +1,172 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # TgBox
2
+
3
+ TgBox is a lightweight cloud storage service for Node.js that uses Telegram as
4
+ its storage medium. It lets you use Telegram chats and channels as personal or
5
+ shared cloud storage for uploading, downloading, searching, and managing files.
6
+
7
+ Telegram provides the storage infrastructure, while TgBox provides a simple
8
+ programmatic interface for using it like a cloud drive.
9
+
10
+ ## Installation
11
+
12
+ [![npm package](https://img.shields.io/npm/v/tgbox)](https://www.npmjs.com/package/tgbox)
13
+
14
+ The package is available on [npm](https://www.npmjs.com/package/tgbox).
15
+
16
+ ```bash
17
+ npm install tgbox
18
+ ```
19
+
20
+ ## How it works
21
+
22
+ TgBox stores files as Telegram document messages in a chat or channel. The
23
+ Telegram message ID acts as the file ID, so files can later be listed, searched,
24
+ downloaded, or deleted through the TgBox API.
25
+
26
+ The target can be:
27
+
28
+ - `me` for Telegram Saved Messages
29
+ - a username such as `@mychannel`
30
+ - another Telegram chat or channel the account can access
31
+
32
+ ## Telegram API credentials
33
+
34
+ You need a Telegram `apiId` and `apiHash` to connect. You can get both values
35
+ from [my.telegram.org](https://my.telegram.org).
36
+
37
+ ```js
38
+ const TgBox = require('tgbox');
39
+
40
+ const storage = new TgBox(
41
+ process.env.TELEGRAM_API_ID,
42
+ process.env.TELEGRAM_API_HASH,
43
+ );
44
+
45
+ await storage.init();
46
+ ```
47
+
48
+ On the first start, TgBox asks for your phone number, login code, and, if
49
+ configured, your 2FA password. The session is then stored locally in
50
+ `session.txt`. Never publish or share this file.
51
+
52
+ ## Upload files
53
+
54
+ ```js
55
+ const result = await storage.upload('./documents/report.pdf', '@mychannel');
56
+
57
+ if (result.success) {
58
+ console.log(result.messageId);
59
+ console.log(result.fileName);
60
+ } else {
61
+ console.error(result.error);
62
+ }
63
+ ```
64
+
65
+ The default chat is `me` (Saved Messages). Telegram file sizes are returned in
66
+ `size` as bytes.
67
+
68
+ ## List files
69
+
70
+ ```js
71
+ const result = await storage.listFiles('@mychannel');
72
+
73
+ if (result.success) {
74
+ result.files.forEach((file) => {
75
+ console.log(file.messageId, file.fileName, file.size, file.mimeType);
76
+ });
77
+ }
78
+ ```
79
+
80
+ Each file object contains, among other fields:
81
+
82
+ - `messageId`
83
+ - `fileName`
84
+ - `size` in bytes
85
+ - `mimeType`
86
+ - `date`
87
+ - `caption`
88
+ - `telegram` with the complete Telegram message and document data
89
+
90
+ ## Search files
91
+
92
+ The search checks the file name, caption, and message ID:
93
+
94
+ ```js
95
+ const result = await storage.searchFiles('report', '@mychannel');
96
+
97
+ result.files.forEach((file) => {
98
+ console.log(file);
99
+ });
100
+ ```
101
+
102
+ For an exact search by ID or file name:
103
+
104
+ ```js
105
+ const result = await storage.findFile('report.pdf', '@mychannel');
106
+
107
+ result.files.forEach((file) => {
108
+ console.log(file.messageId, file.fileName);
109
+ });
110
+ ```
111
+
112
+ All files with the same name are returned.
113
+
114
+ ## Download files
115
+
116
+ ```js
117
+ const result = await storage.download(
118
+ 123,
119
+ './downloads/report.pdf',
120
+ '@mychannel',
121
+ );
122
+
123
+ if (!result.success) {
124
+ console.error(result.error);
125
+ }
126
+ ```
127
+
128
+ ## Delete files
129
+
130
+ You can delete one or more files by their Telegram message IDs:
131
+
132
+ ```js
133
+ await storage.deleteFile(123, '@mychannel');
134
+ await storage.deleteFile([123, 124], '@mychannel');
135
+ ```
136
+
137
+ Deletion is permanent and uses `revoke: true`. Your Telegram account needs the
138
+ required permissions in the target chat or channel.
139
+
140
+ ## Send messages
141
+
142
+ ```js
143
+ const result = await storage.writeMessage(
144
+ 'Backup completed successfully.',
145
+ '@mychannel',
146
+ );
147
+
148
+ console.log(result.messageId);
149
+ ```
150
+
151
+ ## Error handling
152
+
153
+ File operations return a result object:
154
+
155
+ ```js
156
+ {
157
+ success: true,
158
+ files: []
159
+ }
160
+ ```
161
+
162
+ If an operation fails, `success` is set to `false` and the error message is
163
+ available in `error`.
164
+
165
+ ## Security
166
+
167
+ Never commit your API hash, API ID, or `session.txt` to Git or publish them in
168
+ an npm package. Use environment variables and add local files to `.gitignore`.
169
+
170
+ ## License
171
+
172
+ MIT
package/fileService.js ADDED
@@ -0,0 +1,155 @@
1
+ const fs = require('fs');
2
+
3
+ class FileService {
4
+ constructor(client) {
5
+ this.client = client;
6
+ }
7
+
8
+ async upload(filePath, targetChat = 'me') {
9
+ try {
10
+ if (!fs.existsSync(filePath)) {
11
+ throw new Error(`File not found at path: ${filePath}`);
12
+ }
13
+
14
+ const result = await this.client.sendFile(targetChat, {
15
+ file: filePath,
16
+ workers: 2,
17
+ });
18
+
19
+ return {
20
+ success: true,
21
+ messageId: result.id,
22
+ fileName: filePath.split(/[\\/]/).pop(),
23
+ };
24
+ } catch (error) {
25
+ return { success: false, error: error.message };
26
+ }
27
+ }
28
+
29
+ async download(messageId, outputPath, targetChat = 'me') {
30
+ try {
31
+ const messages = await this.client.getMessages(targetChat, {
32
+ ids: [messageId],
33
+ });
34
+
35
+ if (!messages || messages.length === 0) {
36
+ throw new Error('Message not found.');
37
+ }
38
+
39
+ await this.client.downloadMedia(messages[0], {
40
+ outputFile: outputPath,
41
+ });
42
+
43
+ return { success: true, path: outputPath };
44
+ } catch (error) {
45
+ return { success: false, error: error.message };
46
+ }
47
+ }
48
+
49
+ async deleteFile(messageId, targetChat = 'me') {
50
+ const messageIds = Array.isArray(messageId) ? messageId : [messageId];
51
+ if (
52
+ messageIds.length === 0 ||
53
+ messageIds.some((id) => !Number.isInteger(Number(id)) || Number(id) <= 0)
54
+ ) {
55
+ return { success: false, error: 'A valid file message ID is required.' };
56
+ }
57
+
58
+ try {
59
+ await this.client.deleteMessages(
60
+ targetChat,
61
+ messageIds.map(Number),
62
+ { revoke: true },
63
+ );
64
+
65
+ return { success: true, messageIds: messageIds.map(Number) };
66
+ } catch (error) {
67
+ return { success: false, error: error.message };
68
+ }
69
+ }
70
+
71
+ async listFiles(targetChat = 'me') {
72
+ try {
73
+ const files = [];
74
+
75
+ for await (const message of this.client.iterMessages(targetChat, {
76
+ limit: 0,
77
+ })) {
78
+ if (!message.document) {
79
+ continue;
80
+ }
81
+
82
+ const fileNameAttribute = message.document.attributes.find(
83
+ (attribute) => attribute.className === 'DocumentAttributeFilename',
84
+ );
85
+
86
+ files.push({
87
+ messageId: message.id,
88
+ fileName: fileNameAttribute ? fileNameAttribute.fileName : null,
89
+ size: Number(message.document.size),
90
+ mimeType: message.document.mimeType,
91
+ date: message.date,
92
+ caption: message.message || null,
93
+ telegram: {
94
+ message: message.toJSON(),
95
+ document: message.document.toJSON(),
96
+ },
97
+ });
98
+ }
99
+
100
+ return { success: true, files };
101
+ } catch (error) {
102
+ return { success: false, error: error.message };
103
+ }
104
+ }
105
+
106
+ async searchFiles(query, targetChat = 'me') {
107
+ if (typeof query !== 'string' || query.trim() === '') {
108
+ return { success: false, error: 'Search query must not be empty.' };
109
+ }
110
+
111
+ const result = await this.listFiles(targetChat);
112
+ if (!result.success) {
113
+ return result;
114
+ }
115
+
116
+ const normalizedQuery = query.trim().toLowerCase();
117
+ const files = result.files.filter((file) => {
118
+ const fileName = file.fileName || '';
119
+ const caption = file.caption || '';
120
+ const messageId = String(file.messageId);
121
+ return (
122
+ messageId === normalizedQuery ||
123
+ `${fileName} ${caption}`.toLowerCase().includes(normalizedQuery)
124
+ );
125
+ });
126
+
127
+ return { success: true, files };
128
+ }
129
+
130
+ async findFile(identifier, targetChat = 'me') {
131
+ if (typeof identifier !== 'string' && typeof identifier !== 'number') {
132
+ return { success: false, error: 'File ID or name is required.' };
133
+ }
134
+
135
+ const query = String(identifier).trim();
136
+ if (!query) {
137
+ return { success: false, error: 'File ID or name must not be empty.' };
138
+ }
139
+
140
+ const result = await this.listFiles(targetChat);
141
+ if (!result.success) {
142
+ return result;
143
+ }
144
+
145
+ const files = result.files.filter(
146
+ (item) =>
147
+ String(item.messageId) === query ||
148
+ (item.fileName && item.fileName.toLowerCase() === query.toLowerCase()),
149
+ );
150
+
151
+ return { success: true, files };
152
+ }
153
+ }
154
+
155
+ module.exports = FileService;
package/index.js ADDED
@@ -0,0 +1,69 @@
1
+ const { TelegramClient } = require('telegram');
2
+ const { StringSession } = require('telegram/sessions');
3
+ const input = require('input');
4
+ const fs = require('fs');
5
+ const FileService = require('./fileService');
6
+
7
+ class TgBox {
8
+ constructor(apiId, apiHash, sessionFilePath = 'session.txt') {
9
+ if (!apiId || !apiHash) {
10
+ throw new Error('TgBox requires apiId and apiHash.');
11
+ }
12
+ this.apiId = Number(apiId);
13
+ this.apiHash = apiHash;
14
+ this.sessionFilePath = sessionFilePath;
15
+ this.fileService = null;
16
+
17
+ let savedSession = '';
18
+ if (fs.existsSync(this.sessionFilePath)) {
19
+ savedSession = fs.readFileSync(this.sessionFilePath, 'utf8').trim();
20
+ }
21
+
22
+ this.stringSession = new StringSession(savedSession);
23
+ }
24
+
25
+ async init() {
26
+ this.client = new TelegramClient(this.stringSession, this.apiId, this.apiHash, {
27
+ connectionRetries: 5,
28
+ });
29
+ this.fileService = new FileService(this.client);
30
+
31
+ await this.client.start({
32
+ phoneNumber: async () => await input.text('[INFO] Type your phonenumber in: '),
33
+ password: async () => await input.text('[INFO] 2FA Code: '),
34
+ phoneCode: async () => await input.text('[INFO] Telegram code: '),
35
+ onError: (err) => console.log("[ERROR] " + err),
36
+ });
37
+
38
+ console.log('[INFO] Successfully logged in!');
39
+
40
+
41
+ const currentSession = this.client.session.save();
42
+ fs.writeFileSync(this.sessionFilePath, currentSession);
43
+ }
44
+ async upload(filePath, targetChat = 'me') {
45
+ return this.fileService.upload(filePath, targetChat);
46
+ }
47
+
48
+ async download(messageId, outputPath, targetChat = 'me') {
49
+ return this.fileService.download(messageId, outputPath, targetChat);
50
+ }
51
+
52
+ async deleteFile(messageId, targetChat = 'me') {
53
+ return this.fileService.deleteFile(messageId, targetChat);
54
+ }
55
+
56
+ async listFiles(targetChat = 'me') {
57
+ return this.fileService.listFiles(targetChat);
58
+ }
59
+
60
+ async searchFiles(query, targetChat = 'me') {
61
+ return this.fileService.searchFiles(query, targetChat);
62
+ }
63
+
64
+ async findFile(identifier, targetChat = 'me') {
65
+ return this.fileService.findFile(identifier, targetChat);
66
+ }
67
+ }
68
+
69
+ module.exports = TgBox;
package/package.json CHANGED
@@ -1,6 +1,37 @@
1
1
  {
2
2
  "name": "tgbox",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "description": "A lightweight free cloud storage solution using Telegram as backend.",
5
+ "main": "index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/johanneshehl/tgBox.git"
9
+ },
10
+ "homepage": "https://github.com/johanneshehl/tgBox#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/johanneshehl/tgBox/issues"
13
+ },
14
+ "files": [
15
+ "index.js",
16
+ "fileService.js"
17
+ ],
18
+ "scripts": {
19
+ "test": "echo \"Error: no test specified\" && exit 1"
20
+ },
21
+ "keywords": [
22
+ "telegram",
23
+ "storage",
24
+ "cloud",
25
+ "backup",
26
+ "files"
27
+ ],
28
+ "author": "Dein Name",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "axios": "^1.7.9",
32
+ "form-data": "^4.0.1",
33
+ "input": "^1.0.1",
34
+ "telegram": "^2.26.22",
35
+ "tgbox": "^1.0.0"
36
+ }
37
+ }