bt-bot-vpn-tele-manager 1.0.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.
package/nodemon.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "watch": ["index.js", "command/", "lib/"],
3
+ "ignore": [
4
+ "session/*",
5
+ "node_modules",
6
+ "*.log",
7
+ "*.sqlite",
8
+ "*.db"
9
+ ],
10
+ "ext": "js,json"
11
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "bt-bot-vpn-tele-manager",
3
+ "version": "1.0.0",
4
+ "main": "src/index.js",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "nodemon src/index.js"
8
+ },
9
+ "keywords": ["telegraf", "bot", "ssh", "vpn", "manager"],
10
+ "author": "BangTepllo",
11
+ "license": "MIT",
12
+ "description": "",
13
+ "dependencies": {
14
+ "axios": "^1.13.4",
15
+ "dotenv": "^17.2.4",
16
+ "nodemon": "^3.1.11",
17
+ "sqlite": "^5.1.1",
18
+ "sqlite3": "^5.1.7",
19
+ "telegraf": "^4.16.3"
20
+ }
21
+ }
@@ -0,0 +1,34 @@
1
+ import { Markup } from 'telegraf';
2
+
3
+ export const addServerMenu = async (ctx) => {
4
+ const text =
5
+ `📂 <b>ADD NEW SERVER CONFIGURATION</b>\n` +
6
+ `──────────────────────────────\n` +
7
+ `Anda sedang berada di mode <b>Konfigurasi Server</b>.\n` +
8
+ `Silahkan tentukan kategori <b>Protocol</b> untuk server\n` +
9
+ `yang ingin Anda daftarkan ke dalam database.\n\n` +
10
+ `📝 <b>Catatan:</b>\n` +
11
+ `<blockquote>Pastikan domain/IP server sudah aktif\ndan API Manager sudah terinstall.</blockquote>\n` +
12
+ `──────────────────────────────`;
13
+
14
+ await ctx.editMessageText(text, {
15
+ parse_mode: 'HTML',
16
+ ...Markup.inlineKeyboard([
17
+ [
18
+ Markup.button.callback('➕ SSH', 'add_proto_ssh'),
19
+ Markup.button.callback('➕ VMESS', 'add_proto_vmess')
20
+ ],
21
+ [
22
+ Markup.button.callback('➕ VLESS', 'add_proto_vless'),
23
+ Markup.button.callback('➕ TROJAN', 'add_proto_trojan')
24
+ ],
25
+ [
26
+ Markup.button.callback('➕ ZIVPN', 'add_proto_zivpn')
27
+ ],
28
+ [
29
+ Markup.button.callback('⬅️ KEMBALI KE MENU', 'show_menu')
30
+ ]
31
+ ])
32
+ });
33
+ };
34
+
@@ -0,0 +1,33 @@
1
+ import { Markup } from 'telegraf';
2
+
3
+ export const createServerMenu = async (ctx) => {
4
+ const text =
5
+ `🛠 <b>ACCOUNT GENERATOR PANEL</b>\n` +
6
+ `──────────────────────────────\n` +
7
+ `Silahkan pilih <b>Protocol</b> yang ingin Anda gunakan\n` +
8
+ `untuk membuat akun baru pada server kami.\n\n` +
9
+ `💡 <i>Pastikan Anda telah menambahkan server yang\n` +
10
+ `sesuai dengan protocol yang dipilih.</i>\n` +
11
+ `──────────────────────────────`;
12
+
13
+ await ctx.editMessageText(text, {
14
+ parse_mode: 'HTML',
15
+ ...Markup.inlineKeyboard([
16
+ [
17
+ Markup.button.callback('🌐 SSH', 'cr_proto_ssh'),
18
+ Markup.button.callback('📡 VMESS', 'cr_proto_vmess')
19
+ ],
20
+ [
21
+ Markup.button.callback('🛰 VLESS', 'cr_proto_vless'),
22
+ Markup.button.callback('🏹 TROJAN', 'cr_proto_trojan')
23
+ ],
24
+ [
25
+ Markup.button.callback('🔐 ZIVPN', 'cr_proto_zivpn')
26
+ ],
27
+ [
28
+ Markup.button.callback('⬅️ KEMBALI KE MENU', 'show_menu')
29
+ ]
30
+ ])
31
+ });
32
+ };
33
+
@@ -0,0 +1,33 @@
1
+ import { Markup } from 'telegraf';
2
+
3
+ export const deleteServerMenu = async (ctx) => {
4
+ const text =
5
+ `🗑 <b>DELETE SERVER MANAGEMENT</b>\n` +
6
+ `──────────────────────────────\n` +
7
+ `Pilih <b>Protocol</b> dari server yang ingin Anda\n` +
8
+ `hapus secara permanen dari database.\n\n` +
9
+ `⚠️ <b>Peringatan:</b>\n` +
10
+ `<blockquote>Tindakan ini tidak dapat dibatalkan.\nPastikan Anda memilih server yang benar.</blockquote>\n` +
11
+ `──────────────────────────────`;
12
+
13
+ await ctx.editMessageText(text, {
14
+ parse_mode: 'HTML',
15
+ ...Markup.inlineKeyboard([
16
+ [
17
+ Markup.button.callback('🗑 SSH', 'del_proto_ssh'),
18
+ Markup.button.callback('🗑 VMESS', 'del_proto_vmess')
19
+ ],
20
+ [
21
+ Markup.button.callback('🗑 VLESS', 'del_proto_vless'),
22
+ Markup.button.callback('🗑 TROJAN', 'del_proto_trojan')
23
+ ],
24
+ [
25
+ Markup.button.callback('🗑 ZIVPN', 'del_proto_zivpn')
26
+ ],
27
+ [
28
+ Markup.button.callback('⬅️ KEMBALI KE MENU', 'show_menu')
29
+ ]
30
+ ])
31
+ });
32
+ };
33
+
@@ -0,0 +1,69 @@
1
+ import { Markup } from 'telegraf';
2
+ import os from 'os';
3
+
4
+ export const infoBotCommand = async (ctx) => {
5
+ // --- LOGIKA UPTIME YANG DIPERBAIKI ---
6
+ const uptimeSeconds = Math.floor(process.uptime());
7
+ const d = Math.floor(uptimeSeconds / (3600 * 24));
8
+ const h = Math.floor((uptimeSeconds % (3600 * 24)) / 3600);
9
+ const m = Math.floor((uptimeSeconds % 3600) / 60);
10
+ const s = uptimeSeconds % 60;
11
+
12
+ const uptimeString = `${d}d ${h}h ${m}m ${s}s`;
13
+
14
+ // --- LOGIKA WAKTU WIB ---
15
+ const options = {
16
+ timeZone: 'Asia/Jakarta',
17
+ year: 'numeric',
18
+ month: 'long',
19
+ day: 'numeric',
20
+ hour: '2-digit',
21
+ minute: '2-digit',
22
+ second: '2-digit',
23
+ hour12: false
24
+ };
25
+ const wibTime = new Intl.DateTimeFormat('id-ID', options).format(new Date());
26
+
27
+ // Statistik RAM
28
+ const memoryUsage = (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2);
29
+ const totalRam = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2);
30
+
31
+ const text =
32
+ `🤖 <b>SYSTEM BOT INFORMATION</b>\n` +
33
+ `──────────────────────────────\n` +
34
+ `Informasi status kinerja sistem bot saat ini:\n\n` +
35
+ `📊 <b>STATISTIK SISTEM</b>\n` +
36
+ `<blockquote>` +
37
+ `<b>Status:</b> 🟢 Active\n` +
38
+ `<b>Uptime:</b> <code>${uptimeString}</code>\n` +
39
+ `<b>RAM Usage:</b> <code>${memoryUsage} MB</code>\n` +
40
+ `<b>Node.js:</b> <code>${process.version}</code>` +
41
+ `</blockquote>\n` +
42
+ `💻 <b>SERVER HOST</b>\n` +
43
+ `<blockquote>` +
44
+ `<b>OS:</b> ${os.type()} (${os.arch()})\n` +
45
+ `<b>Host RAM:</b> ${totalRam} GB\n` +
46
+ `<b>Timezone:</b> WIB (Jakarta)` +
47
+ `</blockquote>\n` +
48
+ `📅 <i>Last Updated:</i>\n` +
49
+ `<code>${wibTime} WIB</code>\n` +
50
+ `──────────────────────────────`;
51
+
52
+ try {
53
+ await ctx.editMessageText(text, {
54
+ parse_mode: 'HTML',
55
+ ...Markup.inlineKeyboard([
56
+ [
57
+ Markup.button.callback('🔄 REFRESH STATUS', 'info_bot'),
58
+ Markup.button.callback('⬅️ MENU UTAMA', 'show_menu')
59
+ ]
60
+ ])
61
+ });
62
+ } catch (error) {
63
+ // Jika user klik refresh terlalu cepat dan konten sama, abaikan errornya
64
+ if (!error.message.includes('message is not modified')) {
65
+ console.error('Error info_bot:', error.message);
66
+ }
67
+ }
68
+ };
69
+
@@ -0,0 +1,44 @@
1
+ import { Markup } from 'telegraf';
2
+
3
+ export const menuCommand = async (ctx) => {
4
+ const firstName = ctx.from.first_name;
5
+ const username = ctx.from.username ? `@${ctx.from.username}` : '—';
6
+ const idTele = ctx.from.id;
7
+
8
+ // Teks menu dengan tampilan lebih profesional
9
+ const menuText =
10
+ `📱 <b>MAIN DASHBOARD CONTROL</b>\n` +
11
+ `──────────────────────────────\n` +
12
+ `Selamat datang kembali, <b>${firstName}</b>!\n` +
13
+ `Gunakan menu di bawah untuk mengelola server.\n\n` +
14
+ `👤 <b>ACCOUNT INFO</b>\n` +
15
+ `<blockquote>` +
16
+ `<b>User :</b> <code>${username}</code>\n` +
17
+ `<b>ID :</b> <code>${idTele}</code>` +
18
+ `</blockquote>\n` +
19
+ ` Pilih kategori perintah :`;
20
+
21
+ try {
22
+ // Mengedit pesan sebelumnya menjadi menu baru
23
+ await ctx.editMessageText(menuText, {
24
+ parse_mode: 'HTML',
25
+ ...Markup.inlineKeyboard([
26
+ [
27
+ Markup.button.callback('➕ ADD SERVER', 'add_server'),
28
+ Markup.button.callback('🗑️ DELETE SERVER', 'delete_server')
29
+ ],
30
+ [
31
+ Markup.button.callback('🛠️ CREATE ACC', 'create_server'),
32
+ Markup.button.callback('📋 LIST SERVER', 'list_server')
33
+ ],
34
+ [
35
+ Markup.button.callback('🤖 BOT STATUS', 'info_bot')
36
+ ]
37
+ ])
38
+ });
39
+ } catch (error) {
40
+ // Jika gagal edit (misal konten sama), coba kirim pesan baru
41
+ console.error('Gagal mengedit pesan menu:', error.message);
42
+ }
43
+ };
44
+
@@ -0,0 +1,34 @@
1
+ import { Markup } from 'telegraf';
2
+
3
+ export const startCommand = (ctx) => {
4
+ const firstName = ctx.from.first_name;
5
+ const username = ctx.from.username ? `@${ctx.from.username}` : '—';
6
+ const idTele = ctx.from.id;
7
+
8
+ const message =
9
+ `🌟 <b>WELCOME TO PREMIUM BOT MANAGER</b> 🌟\n` +
10
+ `──────────────────────────────\n` +
11
+ `Halo <b>${firstName}</b>, selamat datang di layanan eksklusif\n` +
12
+ `<b>Bot Manager Account</b> by <i>BangTepllo</i>.\n\n` +
13
+ `👑 <b>USER INFORMATION</b>\n` +
14
+ `<blockquote>` +
15
+ `<b>Name:</b> ${firstName}\n` +
16
+ `<b>User:</b> <code>${username}</code>\n` +
17
+ `<b>ID :</b> <code>${idTele}</code>` +
18
+ `</blockquote>\n` +
19
+ `<i>Silahkan klik tombol di bawah untuk mengeksplorasi fitur dan mengelola server Anda.</i>\n` +
20
+ `──────────────────────────────`;
21
+
22
+ ctx.replyWithHTML(message,
23
+ Markup.inlineKeyboard([
24
+ [
25
+ Markup.button.callback('📂 MASUK KE MENU UTAMA', 'show_menu')
26
+ ],
27
+ [
28
+ Markup.button.url('👥 SUPPORT GROUP', 'https://t.me/your_group'),
29
+ Markup.button.url('👤 ADMIN', 'https://t.me/BangTepllo')
30
+ ]
31
+ ])
32
+ );
33
+ };
34
+
package/src/index.js ADDED
@@ -0,0 +1,448 @@
1
+ import { Telegraf, session, Markup } from 'telegraf';
2
+ import 'dotenv/config';
3
+ import axios from 'axios';
4
+ import { initDB } from './lib/database.js';
5
+
6
+ // Import Command Files
7
+ import { startCommand } from './command/start.js';
8
+ import { menuCommand } from './command/menu.js';
9
+ import { addServerMenu } from './command/add_server.js';
10
+ import { createServerMenu } from './command/create_server.js';
11
+ import { infoBotCommand } from './command/info_bot.js';
12
+
13
+
14
+ const bot = new Telegraf(process.env.BOT_TOKEN);
15
+ const ADMIN_ID = process.env.ADMIN_ID;
16
+
17
+ // --- GLOBAL ERROR HANDLER (Mencegah Bot Crash) ---
18
+ bot.catch((err, ctx) => {
19
+ console.error(`❌ Telegraf Error (${ctx.updateType}):`, err.message);
20
+ });
21
+
22
+ async function main() {
23
+ const db = await initDB();
24
+ console.log('✅ Database SQLite Ready!');
25
+
26
+ bot.use(session());
27
+
28
+ // Middleware: Proteksi Admin
29
+ bot.use((ctx, next) => {
30
+ const userId = ctx.from?.id.toString();
31
+ if (userId === ADMIN_ID) return next();
32
+ if (ctx.updateType === 'message') return ctx.reply("❌ Akses Ditolak.");
33
+ });
34
+
35
+ bot.start(startCommand);
36
+ bot.command('menu', menuCommand);
37
+
38
+ bot.action('show_menu', async (ctx) => {
39
+ await ctx.answerCbQuery().catch(() => null);
40
+ menuCommand(ctx);
41
+ });
42
+
43
+ // --- FITUR: ADD SERVER ---
44
+ bot.action('add_server', async (ctx) => {
45
+ await ctx.answerCbQuery().catch(() => null);
46
+ addServerMenu(ctx);
47
+ });
48
+
49
+ bot.action(/^add_proto_(.+)$/, async (ctx) => {
50
+ const proto = ctx.match[1];
51
+ ctx.session = { step: 'WAITING_ADD_NAME', protocol: proto };
52
+ await ctx.answerCbQuery().catch(() => null);
53
+ ctx.editMessageText(`📝 Masukan nama server <b>${proto.toUpperCase()}</b> :`, { parse_mode: 'HTML' });
54
+ });
55
+
56
+ // --- FITUR: CREATE SERVER ---
57
+ bot.action('create_server', async (ctx) => {
58
+ await ctx.answerCbQuery().catch(() => null);
59
+ createServerMenu(ctx);
60
+ });
61
+
62
+ bot.action(/^cr_proto_(.+)$/, async (ctx) => {
63
+ const proto = ctx.match[1];
64
+ try {
65
+ const servers = await db.all(`SELECT * FROM ${proto}_server`);
66
+ if (servers.length === 0) return ctx.answerCbQuery(`❌ Belum ada server ${proto.toUpperCase()}.`,{ show_alert: true });
67
+
68
+ let listText = `⚡ <b>Create Account ${proto.toUpperCase()}</b>\n\nSilahkan pilih server di bawah ini:`;
69
+ const buttons = servers.map(s => Markup.button.callback(`🖥 ${s.name}`, `select_srv_${proto}_${s.id}`));
70
+ const rows = [];
71
+ while(buttons.length) rows.push(buttons.splice(0, 2));
72
+ rows.push([Markup.button.callback('⬅️ KEMBALI', 'show_menu')]);
73
+
74
+ await ctx.editMessageText(listText, { parse_mode: 'HTML', ...Markup.inlineKeyboard(rows) });
75
+ } catch (e) { console.error(e); }
76
+ });
77
+
78
+ // --- LOGIKA: INFO SERVER + BUTTON LANJUTKAN ---
79
+ bot.action(/^select_srv_(.+)_(.+)$/, async (ctx) => {
80
+ const proto = ctx.match[1];
81
+ const srvId = ctx.match[2];
82
+ const server = await db.get(`SELECT * FROM ${proto}_server WHERE id = ?`, [srvId]);
83
+
84
+ await ctx.answerCbQuery("📡 Mengecek status server...").catch(() => null);
85
+ await ctx.editMessageText("⏳ <b>Mendapatkan detail server...</b>", { parse_mode: 'HTML' });
86
+
87
+ try {
88
+ const geo = await axios.get(`http://ip-api.com/json/${server.domain}?fields=country,regionName,city,timezone,isp,query`, { timeout: 8000 });
89
+ const d = geo.data;
90
+
91
+ ctx.session = { protocol: proto, srvName: server.name, domain: server.domain };
92
+
93
+ const infoServer = `
94
+ <blockquote>📡 <b>SERVER INFORMATION</b></blockquote>
95
+
96
+ <blockquote><b>Name:</b> ${server.name}
97
+ <b>Domain:</b> ${server.domain}
98
+ <b>IP Address:</b> ${d.query || '-'}
99
+ <b>ISP:</b> ${d.isp || '-'}
100
+ <b>Region:</b> ${d.city}, ${d.regionName}
101
+ <b>Country:</b> ${d.country}
102
+ </blockquote>
103
+ Apakah Anda ingin melanjutkan pembuatan akun?`;
104
+
105
+ await ctx.editMessageText(infoServer, {
106
+ parse_mode: 'HTML',
107
+ ...Markup.inlineKeyboard([
108
+ [Markup.button.callback('✅ LANJUTKAN', 'next_to_user'), Markup.button.callback('❌ BATAL', 'show_menu')]
109
+ ])
110
+ });
111
+ } catch (error) {
112
+ ctx.session = { protocol: proto, srvName: server.name, domain: server.domain };
113
+ await ctx.editMessageText(`⚠️ <b>Gagal mengambil info ISP.</b>\n<blockquote>Server: ${server.name}</blockquote>\nTetap lanjutkan?`, {
114
+ parse_mode: 'HTML',
115
+ ...Markup.inlineKeyboard([
116
+ [Markup.button.callback('✅ LANJUTKAN', 'next_to_user'), Markup.button.callback('❌ BATAL', 'show_menu')]
117
+ ])
118
+ });
119
+ }
120
+ });
121
+
122
+ bot.action('next_to_user', async (ctx) => {
123
+ const s = ctx.session;
124
+ if (!s) return ctx.answerCbQuery("Sesi habis.").catch(() => null);
125
+ s.step = 'CR_WAIT_USER';
126
+ await ctx.answerCbQuery().catch(() => null);
127
+ await ctx.editMessageText(`
128
+ ✨ <b>Detail ${s.protocol.toUpperCase()}</b>
129
+
130
+ <blockquote><b>Server:</b> ${s.srvName}</blockquote>
131
+ 👤 <b>Masukan Username:</b>`, { parse_mode: 'HTML' });
132
+ });
133
+
134
+ // --- HANDLER INPUT TEKS ---
135
+ bot.on('text', async (ctx) => {
136
+ const state = ctx.session;
137
+ if (!state) return;
138
+
139
+ if (state.step === 'WAITING_ADD_NAME') {
140
+ state.name = ctx.message.text;
141
+ state.step = 'WAITING_ADD_DOMAIN';
142
+ return ctx.reply(`🌐 Masukan domain nya :`);
143
+ }
144
+ if (state.step === 'WAITING_ADD_DOMAIN') {
145
+ await db.run(`INSERT INTO ${state.protocol}_server (name, domain) VALUES (?, ?)`, [state.name, ctx.message.text]);
146
+ ctx.session = null;
147
+ return ctx.reply(`✅ <b>Server Berhasil Ditambahkan!</b>`, {
148
+ parse_mode: 'HTML',
149
+ ...Markup.inlineKeyboard([[Markup.button.callback('🏠 MENU UTAMA', 'show_menu')]])
150
+ });
151
+ }
152
+
153
+ if (state.step === 'CR_WAIT_USER') {
154
+ state.user = ctx.message.text;
155
+ state.step = (['ssh', 'zivpn'].includes(state.protocol)) ? 'CR_WAIT_PASS' : 'CR_WAIT_EXP';
156
+ const label = (state.step === 'CR_WAIT_PASS') ? '🔑 <b>Masukan Password:</b>' : '🗓 <b>Masukan Masa Aktif (Hari):</b>';
157
+
158
+ return ctx.reply(`
159
+ ✨ <b>Detail ${state.protocol.toUpperCase()}</b>
160
+
161
+ <blockquote><b>Server:</b> ${state.srvName}
162
+ <b>User:</b> ${state.user}
163
+ </blockquote>
164
+ ${label}`, { parse_mode: 'HTML' });
165
+ }
166
+
167
+ if (state.step === 'CR_WAIT_PASS') {
168
+ state.pass = ctx.message.text;
169
+ state.step = 'CR_WAIT_EXP';
170
+ return ctx.reply(`
171
+ ✨ <b>Detail ${state.protocol.toUpperCase()}</b>
172
+
173
+ <blockquote><b>Server:</b> ${state.srvName}
174
+ <b>User:</b> ${state.user}
175
+ <b>Pass:</b> ${state.pass}
176
+ </blockquote>
177
+ 🗓 <b>Masukan Masa Aktif (Hari):</b>`, { parse_mode: 'HTML' });
178
+ }
179
+
180
+ if (state.step === 'CR_WAIT_EXP') {
181
+ state.exp = ctx.message.text;
182
+ if (state.protocol === 'zivpn') return showConfirmation(ctx);
183
+ state.step = 'CR_WAIT_IP';
184
+ return ctx.reply(`
185
+ ✨ <b>Detail ${state.protocol.toUpperCase()}</b>
186
+
187
+ <blockquote><b>User:</b> ${state.user}
188
+ <b>Exp:</b> ${state.exp} Days
189
+ </blockquote>
190
+ 📡 <b>Masukan Limit IP:</b>`, { parse_mode: 'HTML' });
191
+ }
192
+
193
+ if (state.step === 'CR_WAIT_IP') {
194
+ state.limitip = ctx.message.text;
195
+ if (state.protocol === 'ssh') return showConfirmation(ctx);
196
+ state.step = 'CR_WAIT_QUOTA';
197
+ return ctx.reply(`
198
+ ✨ <b>Detail ${state.protocol.toUpperCase()}</b>
199
+
200
+ <blockquote><b>User:</b> ${state.user}
201
+ <b>Limit IP:</b> ${state.limitip}
202
+ </blockquote>
203
+ 📊 <b>Masukan Limit Quota (GB):</b>`, { parse_mode: 'HTML' });
204
+ }
205
+
206
+ if (state.step === 'CR_WAIT_QUOTA') {
207
+ state.quota = ctx.message.text;
208
+ return showConfirmation(ctx);
209
+ }
210
+ });
211
+
212
+ async function showConfirmation(ctx) {
213
+ const s = ctx.session;
214
+ if (!s) return;
215
+
216
+ let detail = `
217
+ <blockquote>🛠 <b>KONFIRMASI PEMBUATAN</b></blockquote>
218
+
219
+ <blockquote><b>Protocol:</b> ${s.protocol.toUpperCase()}
220
+ <b>Server:</b> ${s.srvName}
221
+ <b>Username:</b> ${s.user}
222
+ ${s.pass ? `<b>Password:</b> ${s.pass}\n` : ''}<b>Expired:</b> ${s.exp} Days
223
+ ${s.limitip ? `<b>Limit IP:</b> ${s.limitip}\n` : ''}${s.quota ? `<b>Quota:</b> ${s.quota} GB\n` : ''}</blockquote>
224
+ Lanjutkan pembuatan akun?`;
225
+
226
+ await ctx.reply(detail, {
227
+ parse_mode: 'HTML',
228
+ ...Markup.inlineKeyboard([
229
+ [Markup.button.callback('✅ YA, PROSES', 'confirm_create'), Markup.button.callback('❌ BATAL', 'show_menu')]
230
+ ])
231
+ });
232
+ }
233
+
234
+ bot.action('confirm_create', async (ctx) => {
235
+ const s = ctx.session;
236
+ if (!s) return ctx.answerCbQuery("Sesi habis").catch(() => null);
237
+ await ctx.editMessageText("⏳ <b>Sedang meracik akun, mohon tunggu...</b>", { parse_mode: 'HTML' });
238
+
239
+ try {
240
+ let apiUrl = (s.protocol === 'zivpn')
241
+ ? `http://${s.domain}:1000/create-zivpn?username=${s.user}&password=${s.pass}&exp=${s.exp}`
242
+ : `http://${s.domain}:1000/create-${s.protocol}?username=${s.user}&exp=${s.exp}&limitip=${s.limitip}${s.quota ? `&quota=${s.quota}` : ''}${s.pass ? `&password=${s.pass}` : ''}`;
243
+
244
+ const res = await axios.get(apiUrl, { timeout: 25000, validateStatus: (status) => status < 600 });
245
+ const data = (typeof res.data === 'string') ? JSON.parse(res.data.match(/\{[\s\S]*\}/)[0]) : res.data;
246
+
247
+ if (data.status === "failed" || data.status === "error") {
248
+ return ctx.reply(`❌ <b>Gagal:</b> ${data.message || 'Error VPS.'}`, { parse_mode: 'HTML' });
249
+ }
250
+
251
+ let msg = "";
252
+ if (['vmess', 'vless', 'trojan'].includes(s.protocol)) {
253
+ msg = `
254
+ <blockquote><b>◇━━━━━━━━━━━━━━━━━◇</b>
255
+ <b> ${s.protocol.toUpperCase()}</b>
256
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
257
+ <b>Remarks :</b> <code>${data.user}</code>
258
+ <b>Domain :</b> <code>${s.domain}</code>
259
+ <b>Limit Quota :</b> <code>${data.quota || s.quota || 'Unlimited'}</code>
260
+ <b>Limit IP :</b> <code>${s.limitip}</code>
261
+ <b>ID/Pass :</b> <code>${data.password || data.uuid}</code>
262
+ <b>Port TLS :</b> <code>400-900</code>
263
+ <b>Port NTLS :</b> <code>80, 8080, 8081-9999</code>
264
+ <b>Network :</b> <code>WS or gRPC</code>
265
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
266
+ <b>${s.protocol.toUpperCase()} WS TLS</b>
267
+ <code>${data.link_tls}</code>
268
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
269
+ <b>${s.protocol.toUpperCase()} WS NO TLS</b>
270
+ <code>${data.link_none}</code>
271
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
272
+ <b>${s.protocol.toUpperCase()} gRPC</b>
273
+ <code>${data.link_grpc}</code>
274
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
275
+ <b>Format OpenClash :</b>
276
+ <b>https://${s.domain}:81/${s.protocol}-${data.user}.txt</b>
277
+ <b>◇━━━━━━━━━━━━━━━━━◇</b></blockquote>`;
278
+ } else if (s.protocol === 'ssh') {
279
+ msg = `
280
+ <blockquote><b>◇━━━━━━━━━━━━━━━━━◇</b>
281
+ <b> SSH</b>
282
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
283
+ <b>Username :</b> <code>${s.user}</code>
284
+ <b>Password :</b> <code>${s.pass}</code>
285
+ <b>Host :</b> <code>${s.domain}</code>
286
+ <b>Limit IP :</b> <code>${s.limitip}</code>
287
+ <b>Expired :</b> <code>${s.exp} Days</code>
288
+ <b>Port OpenSSH :</b> <code>443, 80, 22</code>
289
+ <b>Port Dropbear :</b> <code>443, 109</code>
290
+ <b>Port SSH WS :</b> <code>80, 8080, 8081-9999</code>
291
+ <b>Port SSH UDP :</b> <code>1-65535 </code>
292
+ <b>Port SSH SSL WS :</b> <code>443</code>
293
+ <b>Port SSL/TLS :</b> <code>400-900</code>
294
+ <b>Port OVPN WS SSL :</b> <code>443</code>
295
+ <b>Port OVPN SSL :</b> <code>443</code>
296
+ <b>Port OVPN TCP :</b> <code>443, 1194</code>
297
+ <b>Port OVPN UDP :</b> <code>2200</code>
298
+ <b>BadVPN UDP :</b> <code>7100, 7300, 7300</code>
299
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
300
+ <b>Format Config:</b>
301
+ <b>WS TLS: ${s.domain}:443@${s.user}:${s.pass}</b>
302
+ <b>WS NTLS: ${s.domain}:80@${s.user}:${s.pass}</b>
303
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
304
+ <b>OVPN Download : https://${s.domain}:81/</b>
305
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
306
+ <b>Save Link Account: https://${s.domain}:81/ssh-${s.user}.txt</b>
307
+ <b>◇━━━━━━━━━━━━━━━━━◇</b></blockquote>`;
308
+ } else if (s.protocol === 'zivpn') {
309
+ msg = `
310
+ <blockquote><b>◇━━━━━━━━━━━━━━━━━◇</b>
311
+ <b>ZIVPN PREMIUM</b>
312
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
313
+ <b>Host :</b> <code>${s.domain}</code>
314
+ <b>Username :</b> <code>${s.user}</code>
315
+ <b>Password :</b> <code>${s.pass}</code>
316
+ <b>Expired :</b> <code>${s.exp}</code> Days
317
+ <b>◇━━━━━━━━━━━━━━━━━◇</b>
318
+ <b>Download Config:</b>
319
+ http://${s.domain}:81/zivpn-${s.user}.zip
320
+ <b>◇━━━━━━━━━━━━━━━━━◇</b></blockquote>`;
321
+ }
322
+
323
+ await ctx.editMessageText(msg, { parse_mode: 'HTML' });
324
+ await ctx.reply(`✅ <b>Akun Berhasil Dibuat!</b>`, {
325
+ parse_mode: 'HTML',
326
+ ...Markup.inlineKeyboard([[Markup.button.callback('🏠 MENU UTAMA', 'show_menu')]])
327
+ });
328
+
329
+ } catch (e) {
330
+ const safeError = e.message.replace(/</g, '&lt;').replace(/>/g, '&gt;');
331
+ ctx.reply(`❌ <b>Error:</b> VPS Offline\n<blockquote>Log: ${safeError}</blockquote>`, { parse_mode: 'HTML' });
332
+ }
333
+ ctx.session = null;
334
+ });
335
+
336
+ bot.action('cancel_create', async (ctx) => {
337
+ await ctx.answerCbQuery().catch(() => null);
338
+ ctx.session = null;
339
+ ctx.editMessageText("❌ <b>Pembuatan akun dibatalkan.</b>", {
340
+ parse_mode: 'HTML',
341
+ ...Markup.inlineKeyboard([[Markup.button.callback('🏠 MENU UTAMA', 'show_menu')]])
342
+ });
343
+ });
344
+
345
+ // --- FITUR: DELETE SERVER ---
346
+ bot.action('delete_server', async (ctx) => {
347
+ await ctx.answerCbQuery().catch(() => null);
348
+ const { deleteServerMenu } = await import('./command/delete_server.js');
349
+ deleteServerMenu(ctx);
350
+ });
351
+
352
+ bot.action(/^del_proto_(.+)$/, async (ctx) => {
353
+ const proto = ctx.match[1];
354
+ try {
355
+ const servers = await db.all(`SELECT * FROM ${proto}_server`);
356
+ if (servers.length === 0) return ctx.answerCbQuery(`❌ Belum ada server ${proto.toUpperCase()}.`, { show_alert: true });
357
+
358
+ let listText = `🗑 <b>Hapus Server ${proto.toUpperCase()}</b>\n\nSilahkan pilih server yang ingin dihapus:`;
359
+ // Perhatikan callback-nya: select_del_...
360
+ const buttons = servers.map(s => Markup.button.callback(`🗑 ${s.name}`, `select_del_${proto}_${s.id}`));
361
+ const rows = [];
362
+ while(buttons.length) rows.push(buttons.splice(0, 2));
363
+ rows.push([Markup.button.callback('⬅️ KEMBALI', 'delete_server')]);
364
+
365
+ await ctx.editMessageText(listText, { parse_mode: 'HTML', ...Markup.inlineKeyboard(rows) });
366
+ } catch (e) { console.error(e); }
367
+ });
368
+
369
+ bot.action(/^select_del_(.+)_(.+)$/, async (ctx) => {
370
+ const proto = ctx.match[1];
371
+ const srvId = ctx.match[2];
372
+ const server = await db.get(`SELECT * FROM ${proto}_server WHERE id = ?`, [srvId]);
373
+
374
+ await ctx.answerCbQuery("📡 Mengambil data server...").catch(() => null);
375
+
376
+ try {
377
+ const geo = await axios.get(`http://ip-api.com/json/${server.domain}?fields=country,regionName,city,timezone,isp,query`, { timeout: 8000 });
378
+ const d = geo.data;
379
+
380
+ // Simpan data di session untuk proses eksekusi delete
381
+ ctx.session = { delProto: proto, delId: srvId, delName: server.name };
382
+
383
+ const infoServer = `
384
+ <blockquote>📡 <b>SERVER INFORMATION (DELETE)</b></blockquote>
385
+
386
+ <blockquote><b>Name:</b> ${server.name}
387
+ <b>Domain:</b> ${server.domain}
388
+ <b>IP Address:</b> ${d.query || '-'}
389
+ <b>ISP:</b> ${d.isp || '-'}
390
+ <b>Region:</b> ${d.city}, ${d.regionName}
391
+ <b>Country:</b> ${d.country}
392
+ </blockquote>
393
+ Apakah Anda yakin ingin <b>MENGHAPUS</b> server ini?`;
394
+
395
+ await ctx.editMessageText(infoServer, {
396
+ parse_mode: 'HTML',
397
+ ...Markup.inlineKeyboard([
398
+ [Markup.button.callback('🗑 YA, HAPUS', 'execute_delete'), Markup.button.callback('❌ BATAL', 'delete_server')]
399
+ ])
400
+ });
401
+ } catch (error) {
402
+ ctx.session = { delProto: proto, delId: srvId, delName: server.name };
403
+ await ctx.editMessageText(`⚠️ <b>Gagal mengambil info GeoIP.</b>\n<blockquote>Server: ${server.name}</blockquote>\nTetap hapus server ini?`, {
404
+ parse_mode: 'HTML',
405
+ ...Markup.inlineKeyboard([
406
+ [Markup.button.callback('🗑 YA, HAPUS', 'execute_delete'), Markup.button.callback('❌ BATAL', 'delete_server')]
407
+ ])
408
+ });
409
+ }
410
+ });
411
+
412
+ bot.action('execute_delete', async (ctx) => {
413
+ const s = ctx.session;
414
+ if (!s || !s.delId) return ctx.answerCbQuery("Sesi habis.").catch(() => null);
415
+
416
+ try {
417
+ await db.run(`DELETE FROM ${s.delProto}_server WHERE id = ?`, [s.delId]);
418
+
419
+ await ctx.answerCbQuery(`✅ Server ${s.delName} berhasil dihapus!`, { show_alert: true });
420
+
421
+ // Kembali ke menu utama setelah hapus
422
+ ctx.session = null;
423
+ const { menuCommand } = await import('./command/menu.js');
424
+ return menuCommand(ctx);
425
+ } catch (e) {
426
+ console.error(e);
427
+ ctx.reply("❌ Gagal menghapus server dari database.");
428
+ }
429
+ });
430
+
431
+ // --- FITUR: INFO BOT ---
432
+ bot.action('info_bot', async (ctx) => {
433
+ await ctx.answerCbQuery("📡 Mengambil data sistem...").catch(() => null);
434
+ infoBotCommand(ctx);
435
+ });
436
+
437
+
438
+
439
+ bot.launch().then(() => console.log('🚀 Bot Running with HTML Mode!'));
440
+ }
441
+
442
+ main();
443
+
444
+ // Anti-crash global
445
+ process.on('unhandledRejection', (reason, promise) => {
446
+ console.log('⚠️ Unhandled Rejection:', reason);
447
+ });
448
+
@@ -0,0 +1,22 @@
1
+ import sqlite3 from 'sqlite3';
2
+ import { open } from 'sqlite';
3
+
4
+ export async function initDB() {
5
+ const db = await open({
6
+ filename: './database.db',
7
+ driver: sqlite3.Database
8
+ });
9
+
10
+ // Buat tabel untuk setiap protokol jika belum ada
11
+ const protocols = ['ssh', 'vmess', 'vless', 'trojan', 'zivpn'];
12
+ for (const proto of protocols) {
13
+ await db.exec(`
14
+ CREATE TABLE IF NOT EXISTS ${proto}_server (
15
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16
+ name TEXT,
17
+ domain TEXT
18
+ )
19
+ `);
20
+ }
21
+ return db;
22
+ }