Ikyy commited on
Commit
104f785
Β·
verified Β·
1 Parent(s): ccae17a

Create server.js

Browse files
Files changed (1) hide show
  1. server.js +270 -0
server.js ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // server.js (root level untuk Hugging Face)
2
+ import express from "express";
3
+ import { createServer } from "http";
4
+ import { Server } from "socket.io";
5
+ import makeWASocket, {
6
+ useMultiFileAuthState,
7
+ DisconnectReason,
8
+ Browsers,
9
+ makeCacheableSignalKeyStore
10
+ } from "@whiskeysockets/baileys";
11
+ import Pino from "pino";
12
+ import { Boom } from "@hapi/boom";
13
+ import fs from "fs";
14
+ import path from "path";
15
+ import { fileURLToPath } from "url";
16
+ import NodeCache from "node-cache";
17
+ import { v4 as uuidv4 } from "uuid";
18
+
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = path.dirname(__filename);
21
+
22
+ const app = express();
23
+ const httpServer = createServer(app);
24
+ const io = new Server(httpServer, {
25
+ cors: {
26
+ origin: process.env.FRONTEND_URL || "*",
27
+ methods: ["GET", "POST"]
28
+ }
29
+ });
30
+
31
+ // Session cache - expire setelah 10 menit
32
+ const sessionCache = new NodeCache({ stdTTL: 600, checkperiod: 60 });
33
+ const activeSessions = new Map();
34
+
35
+ // Middleware
36
+ app.use(express.json());
37
+ app.use(express.urlencoded({ extended: true }));
38
+
39
+ // Serve static files from frontend build
40
+ const frontendPath = path.join(__dirname, "frontend", "dist");
41
+ if (fs.existsSync(frontendPath)) {
42
+ app.use(express.static(frontendPath));
43
+ }
44
+
45
+ // Health check
46
+ app.get("/health", (req, res) => {
47
+ res.json({
48
+ status: "ok",
49
+ activeSessions: activeSessions.size,
50
+ timestamp: new Date().toISOString()
51
+ });
52
+ });
53
+
54
+ // Serve frontend for all other routes
55
+ app.get("*", (req, res) => {
56
+ const indexPath = path.join(__dirname, "frontend", "dist", "index.html");
57
+ if (fs.existsSync(indexPath)) {
58
+ res.sendFile(indexPath);
59
+ } else {
60
+ res.status(404).json({ error: "Frontend not found. Please build the frontend first." });
61
+ }
62
+ });
63
+
64
+ // Cleanup function
65
+ function cleanupSession(sessionId) {
66
+ const sessionDir = path.join(__dirname, "sessions", sessionId);
67
+
68
+ try {
69
+ if (fs.existsSync(sessionDir)) {
70
+ fs.rmSync(sessionDir, { recursive: true, force: true });
71
+ console.log(`βœ… Session ${sessionId} cleaned up`);
72
+ }
73
+
74
+ activeSessions.delete(sessionId);
75
+ sessionCache.del(sessionId);
76
+ } catch (error) {
77
+ console.error(`❌ Cleanup error for ${sessionId}:`, error.message);
78
+ }
79
+ }
80
+
81
+ // Socket.IO connection handler
82
+ io.on("connection", (socket) => {
83
+ console.log(`πŸ”Œ Client connected: ${socket.id}`);
84
+
85
+ socket.on("start-pairing", async ({ phoneNumber }) => {
86
+ // Generate unique session ID
87
+ const sessionId = uuidv4();
88
+ const sessionDir = path.join(__dirname, "sessions", sessionId);
89
+
90
+ try {
91
+ // Validasi nomor - support semua negara
92
+ const cleanNumber = phoneNumber.replace(/[^\d]/g, '');
93
+
94
+ if (!cleanNumber || cleanNumber.length < 10 || cleanNumber.length > 15) {
95
+ socket.emit("error", { message: "Invalid phone number! Must be 10-15 digits" });
96
+ return;
97
+ }
98
+
99
+ // Pastikan dimulai dengan country code (bukan 0)
100
+ if (cleanNumber.startsWith('0')) {
101
+ socket.emit("error", { message: "Phone number must include country code (e.g., 1, 44, 62, 91)" });
102
+ return;
103
+ }
104
+
105
+ // Check session limit
106
+ if (activeSessions.size >= 50) {
107
+ socket.emit("error", { message: "Server is full, please try again later" });
108
+ return;
109
+ }
110
+
111
+ socket.emit("status", { message: "Starting connection...", sessionId });
112
+
113
+ // Create session directory
114
+ if (!fs.existsSync(sessionDir)) {
115
+ fs.mkdirSync(sessionDir, { recursive: true });
116
+ }
117
+
118
+ const { state, saveCreds } = await useMultiFileAuthState(sessionDir);
119
+
120
+ const sock = makeWASocket({
121
+ auth: {
122
+ creds: state.creds,
123
+ keys: makeCacheableSignalKeyStore(
124
+ state.keys,
125
+ Pino().child({ level: "fatal", stream: "store" })
126
+ )
127
+ },
128
+ browser: Browsers.ubuntu("Chrome"),
129
+ logger: Pino({ level: "silent" }),
130
+ printQRInTerminal: false,
131
+ syncFullHistory: false,
132
+ markOnlineOnConnect: false
133
+ });
134
+
135
+ // Store socket reference
136
+ activeSessions.set(sessionId, { sock, phoneNumber, socketId: socket.id });
137
+ sessionCache.set(sessionId, { phoneNumber, createdAt: Date.now() });
138
+
139
+ sock.ev.on("creds.update", saveCreds);
140
+
141
+ // Connection update handler
142
+ sock.ev.on("connection.update", async (update) => {
143
+ const { connection, lastDisconnect } = update;
144
+
145
+ if (connection === "close") {
146
+ const shouldReconnect =
147
+ (lastDisconnect?.error)?.output?.statusCode !== DisconnectReason.loggedOut;
148
+
149
+ if (shouldReconnect) {
150
+ socket.emit("error", { message: "Connection closed, please try again" });
151
+ }
152
+
153
+ cleanupSession(sessionId);
154
+ } else if (connection === "open") {
155
+ socket.emit("status", { message: "Connected! Sending creds.json..." });
156
+
157
+ console.log(`βœ… Connected for session ${sessionId}`);
158
+
159
+ // Tunggu 5 detik
160
+ await new Promise(resolve => setTimeout(resolve, 5000));
161
+
162
+ try {
163
+ // Baca creds.json
164
+ const credsPath = path.join(sessionDir, "creds.json");
165
+ const credsContent = fs.readFileSync(credsPath, "utf8");
166
+
167
+ // Kirim file ke nomor bot
168
+ await sock.sendMessage(`${cleanNumber}@s.whatsapp.net`, {
169
+ document: Buffer.from(credsContent),
170
+ mimetype: "application/json",
171
+ fileName: "creds.json",
172
+ caption: "βœ… Session created successfully!\n\nSave this file securely."
173
+ });
174
+
175
+ socket.emit("success", {
176
+ message: "creds.json sent successfully!",
177
+ sessionId
178
+ });
179
+
180
+ console.log(`πŸ“€ Creds sent to ${cleanNumber}`);
181
+
182
+ // Tunggu sebentar buat mastiin file terkirim
183
+ await new Promise(resolve => setTimeout(resolve, 2000));
184
+
185
+ // Disconnect & cleanup
186
+ await sock.logout();
187
+ cleanupSession(sessionId);
188
+
189
+ socket.emit("status", { message: "Done! Session cleaned up." });
190
+
191
+ } catch (error) {
192
+ console.error(`❌ Send file error:`, error);
193
+ socket.emit("error", { message: "Failed to send file" });
194
+ cleanupSession(sessionId);
195
+ }
196
+ }
197
+ });
198
+
199
+ // Request pairing code
200
+ setTimeout(async () => {
201
+ try {
202
+ if (!sock.authState.creds.registered) {
203
+ const code = await sock.requestPairingCode(cleanNumber);
204
+ socket.emit("pairing-code", { code, sessionId });
205
+ console.log(`πŸ“± Pairing code for ${cleanNumber}: ${code}`);
206
+ }
207
+ } catch (error) {
208
+ console.error(`❌ Pairing code error:`, error);
209
+ socket.emit("error", { message: "Failed to get pairing code" });
210
+ cleanupSession(sessionId);
211
+ }
212
+ }, 3000);
213
+
214
+ } catch (error) {
215
+ console.error(`❌ Connection error:`, error);
216
+ socket.emit("error", { message: error.message || "An error occurred" });
217
+ cleanupSession(sessionId);
218
+ }
219
+ });
220
+
221
+ socket.on("disconnect", () => {
222
+ console.log(`πŸ”Œ Client disconnected: ${socket.id}`);
223
+
224
+ // Cleanup sessions milik socket ini
225
+ for (const [sessionId, data] of activeSessions.entries()) {
226
+ if (data.socketId === socket.id) {
227
+ cleanupSession(sessionId);
228
+ }
229
+ }
230
+ });
231
+ });
232
+
233
+ // Cleanup old sessions every 5 minutes
234
+ setInterval(() => {
235
+ const sessionsDir = path.join(__dirname, "sessions");
236
+
237
+ if (fs.existsSync(sessionsDir)) {
238
+ const sessions = fs.readdirSync(sessionsDir);
239
+ const now = Date.now();
240
+ const maxAge = 10 * 60 * 1000; // 10 menit
241
+
242
+ sessions.forEach(sessionId => {
243
+ const sessionData = sessionCache.get(sessionId);
244
+
245
+ if (!sessionData || (now - sessionData.createdAt) > maxAge) {
246
+ cleanupSession(sessionId);
247
+ }
248
+ });
249
+ }
250
+ }, 5 * 60 * 1000);
251
+
252
+ // Graceful shutdown
253
+ process.on("SIGINT", () => {
254
+ console.log("\n⏹️ Shutting down...");
255
+
256
+ // Cleanup semua active sessions
257
+ for (const sessionId of activeSessions.keys()) {
258
+ cleanupSession(sessionId);
259
+ }
260
+
261
+ process.exit(0);
262
+ });
263
+
264
+ const PORT = process.env.PORT || 7860;
265
+
266
+ httpServer.listen(PORT, "0.0.0.0", () => {
267
+ console.log(`πŸš€ Server running on http://0.0.0.0:${PORT}`);
268
+ console.log(`πŸ”Œ Socket.IO ready`);
269
+ console.log(`πŸ“± Baileys version: 6.7.20\n`);
270
+ });