@@ -13,6 +13,7 @@ import NodeCache from '@cacheable/node-cache';
1313import fs from 'fs/promises' ;
1414import pino from 'pino' ;
1515import cfonts from 'cfonts' ;
16+ import inquirer from 'inquirer' ;
1617
1718const storeLogger = pino ( { level : 'fatal' , stream : 'store' } ) ;
1819const silentLogger = pino ( { level : 'silent' } ) ;
@@ -28,13 +29,8 @@ const displayBanner = () => {
2829 space : true
2930 } ) ;
3031
31- const terminalWidth = process . stdout . columns ;
32- const centered = output . string
33- . split ( '\n' )
34- . map ( line => ' ' . repeat ( Math . max ( 0 , ( terminalWidth - line . length ) >> 1 ) ) + line )
35- . join ( '\n' ) ;
36-
37- console . log ( centered ) ;
32+ const w = process . stdout . columns ;
33+ console . log ( output . string . split ( '\n' ) . map ( l => ' ' . repeat ( Math . max ( 0 , ( w - l . length ) >> 1 ) ) + l ) . join ( '\n' ) ) ;
3834} ;
3935
4036displayBanner ( ) ;
@@ -50,21 +46,20 @@ global.plugins = loader.plugins;
5046
5147const msgRetryCounterCache = new NodeCache ( ) ;
5248const groupMetadataCache = new Map ( ) ;
53- const CACHE_TTL = 5 * 60 * 1000 ;
54-
49+ const CACHE_TTL = 300000 ;
50+ const MAX_RECONNECT = 5 ;
5551const RECONNECT_STRATEGIES = Object . freeze ( {
56- 408 : { action : 'restart' , delay : 2000 , message : 'Connection timed out' } ,
57- 503 : { action : 'restart' , delay : 3000 , message : 'Service unavailable' } ,
58- 428 : { action : 'restart' , delay : 2000 , message : 'Connection closed' } ,
59- 515 : { action : 'restart' , delay : 2000 , message : 'Connection closed' } ,
60- 401 : { action : 'reset' , delay : 1000 , message : 'Session logged out' } ,
61- 403 : { action : 'reset' , delay : 1000 , message : 'Account banned' } ,
62- 405 : { action : 'reset' , delay : 1000 , message : 'Session not logged in' }
52+ 408 : { action : 'restart' , delay : 2000 , msg : 'Connection timed out' } ,
53+ 503 : { action : 'restart' , delay : 3000 , msg : 'Service unavailable' } ,
54+ 428 : { action : 'restart' , delay : 2000 , msg : 'Connection closed' } ,
55+ 515 : { action : 'restart' , delay : 2000 , msg : 'Connection closed' } ,
56+ 401 : { action : 'reset' , delay : 1000 , msg : 'Session logged out' } ,
57+ 403 : { action : 'reset' , delay : 1000 , msg : 'Account banned' } ,
58+ 405 : { action : 'reset' , delay : 1000 , msg : 'Session not logged in' }
6359} ) ;
6460
6561let reconnectAttempts = 0 ;
66- const MAX_RECONNECT_ATTEMPTS = 5 ;
67- const EXPONENTIAL_BACKOFF = true ;
62+ let handlerModule = null ;
6863
6964const resetSession = async ( ) => {
7065 try {
@@ -75,12 +70,6 @@ const resetSession = async () => {
7570 }
7671} ;
7772
78- const calculateDelay = ( baseDelay , attempt ) => {
79- return EXPONENTIAL_BACKOFF
80- ? baseDelay * Math . pow ( 2 , attempt - 1 )
81- : baseDelay * attempt ;
82- } ;
83-
8473const handleReconnect = async ( statusCode ) => {
8574 const strategy = RECONNECT_STRATEGIES [ statusCode ] ;
8675
@@ -89,19 +78,15 @@ const handleReconnect = async (statusCode) => {
8978 process . exit ( 1 ) ;
9079 }
9180
92- reconnectAttempts ++ ;
93-
94- if ( reconnectAttempts > MAX_RECONNECT_ATTEMPTS ) {
81+ if ( ++ reconnectAttempts > MAX_RECONNECT ) {
9582 log . fatal ( 'Max reconnection attempts reached. Exiting...' ) ;
9683 process . exit ( 1 ) ;
9784 }
9885
99- const delay = calculateDelay ( strategy . delay , reconnectAttempts ) ;
100- log . warn ( `${ strategy . message } . Reconnecting in ${ delay } ms... (Attempt ${ reconnectAttempts } /${ MAX_RECONNECT_ATTEMPTS } )` ) ;
86+ const delay = strategy . delay * Math . pow ( 2 , reconnectAttempts - 1 ) ;
87+ log . warn ( `${ strategy . msg } . Reconnecting in ${ delay } ms... (${ reconnectAttempts } /${ MAX_RECONNECT } )` ) ;
10188
102- if ( strategy . action === 'reset' ) {
103- await resetSession ( ) ;
104- }
89+ if ( strategy . action === 'reset' ) await resetSession ( ) ;
10590
10691 await new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
10792 return startWA ( ) ;
@@ -111,9 +96,7 @@ const fetchGroupMetadata = async (conn, groupId) => {
11196 const cached = groupMetadataCache . get ( groupId ) ;
11297 const now = Date . now ( ) ;
11398
114- if ( cached && ( now - cached . timestamp ) < CACHE_TTL ) {
115- return cached . data ;
116- }
99+ if ( cached && ( now - cached . timestamp ) < CACHE_TTL ) return cached . data ;
117100
118101 try {
119102 const metadata = await conn . groupMetadata ( groupId ) ;
@@ -127,6 +110,31 @@ const fetchGroupMetadata = async (conn, groupId) => {
127110
128111const isValidGroupId = ( id ) => id && id !== 'status@broadcast' && id . endsWith ( '@g.us' ) ;
129112
113+ const getPairingNumber = async ( ) => {
114+ try {
115+ const { phoneNumber } = await inquirer . prompt ( [
116+ {
117+ type : 'input' ,
118+ name : 'phoneNumber' ,
119+ message : 'Masukkan nomor WhatsApp (format: 62882xxxxxxxx):' ,
120+ validate : ( input ) => {
121+ const cleaned = input . replace ( / \D / g, '' ) ;
122+ if ( ! cleaned ) return 'Nomor telepon tidak boleh kosong' ;
123+ if ( ! cleaned . startsWith ( '62' ) ) return 'Nomor harus diawali dengan 62' ;
124+ if ( cleaned . length < 10 || cleaned . length > 15 ) return 'Panjang nomor tidak valid (10-15 digit)' ;
125+ return true ;
126+ } ,
127+ filter : ( input ) => input . replace ( / \D / g, '' )
128+ }
129+ ] ) ;
130+
131+ return phoneNumber ;
132+ } catch ( err ) {
133+ log . error ( err . isTtyError ? 'Terminal tidak mendukung prompt interaktif' : `Error: ${ err . message } ` ) ;
134+ process . exit ( 1 ) ;
135+ }
136+ } ;
137+
130138async function startWA ( ) {
131139 const { state, saveCreds } = await useMultiFileAuthState ( 'sessions' ) ;
132140 const { version, isLatest } = await fetchLatestBaileysVersion ( ) ;
@@ -147,12 +155,8 @@ async function startWA() {
147155 defaultQueryTimeoutMs : 60000 ,
148156 emitOwnEvents : false ,
149157 fireInitQueries : true ,
150- getMessage : async ( key ) => {
151- return { conversation : '' } ;
152- } ,
153- patchMessageBeforeSending : ( message ) => {
154- return message ;
155- } ,
158+ getMessage : async ( ) => ( { conversation : '' } ) ,
159+ patchMessageBeforeSending : ( msg ) => msg ,
156160 countryCode : 'ID' ,
157161 maxMsgRetryCount : 3 ,
158162 retryRequestDelayMs : 3000 ,
@@ -165,9 +169,11 @@ async function startWA() {
165169 conn . chats ??= { } ;
166170
167171 if ( ! conn . authState . creds . registered ) {
172+ const pairingNumber = await getPairingNumber ( ) ;
173+
168174 setTimeout ( async ( ) => {
169175 try {
170- const code = await conn . requestPairingCode ( PAIRING_NUMBER ) ;
176+ const code = await conn . requestPairingCode ( pairingNumber ) ;
171177 log . info ( `Pairing Code: ${ code } ` ) ;
172178 } catch ( err ) {
173179 log . error ( `Failed to get pairing code: ${ err . message } ` ) ;
@@ -199,25 +205,19 @@ async function startWA() {
199205
200206 conn . ev . on ( 'group-participants.update' , async ( { id } ) => {
201207 if ( ! isValidGroupId ( id ) ) return ;
202-
203208 const metadata = await fetchGroupMetadata ( conn , id ) ;
204209 if ( metadata ) conn . chats [ id ] = metadata ;
205210 } ) ;
206211
207212 conn . ev . on ( 'groups.update' , async ( updates ) => {
208213 const validGroups = updates . filter ( ( { id } ) => isValidGroupId ( id ) ) ;
209-
210- if ( validGroups . length === 0 ) return ;
214+ if ( ! validGroups . length ) return ;
211215
212- const promises = validGroups . map ( async ( { id } ) => {
216+ await Promise . allSettled ( validGroups . map ( async ( { id } ) => {
213217 const metadata = await fetchGroupMetadata ( conn , id ) ;
214218 if ( metadata ) conn . chats [ id ] = metadata ;
215- } ) ;
216-
217- await Promise . allSettled ( promises ) ;
219+ } ) ) ;
218220 } ) ;
219-
220- let handlerModule = null ;
221221
222222 conn . ev . on ( 'messages.upsert' , async ( { messages } ) => {
223223 const msg = messages [ 0 ] ;
@@ -226,15 +226,11 @@ async function startWA() {
226226 try {
227227 const m = await serialize ( conn , msg ) ;
228228
229- if ( m . chat . endsWith ( '@broadcast' ) ||
230- m . type === 'protocolMessage' ||
231- m . isBot ) return ;
229+ if ( m . chat . endsWith ( '@broadcast' ) || m . type === 'protocolMessage' || m . isBot ) return ;
232230
233231 if ( m . message ) printMessage ( m , conn ) ;
234232
235- if ( ! handlerModule ) {
236- handlerModule = await import ( './handler.js' ) ;
237- }
233+ if ( ! handlerModule ) handlerModule = await import ( './handler.js' ) ;
238234
239235 await handlerModule . default ( conn , m ) ;
240236 } catch ( err ) {
@@ -256,18 +252,14 @@ process.on('uncaughtException', (err) => {
256252} ) ;
257253
258254process . on ( 'unhandledRejection' , ( reason , promise ) => {
259- log . error ( `Unhandled Rejection at: ${ promise } ` ) ;
260- log . error ( `Reason: ${ reason } ` ) ;
255+ log . error ( `Unhandled Rejection at: ${ promise } , Reason: ${ reason } ` ) ;
261256} ) ;
262257
263- process . on ( 'SIGINT' , async ( ) => {
258+ const cleanup = async ( ) => {
264259 log . info ( 'Shutting down gracefully...' ) ;
265260 groupMetadataCache . clear ( ) ;
266261 process . exit ( 0 ) ;
267- } ) ;
262+ } ;
268263
269- process . on ( 'SIGTERM' , async ( ) => {
270- log . info ( 'Shutting down gracefully...' ) ;
271- groupMetadataCache . clear ( ) ;
272- process . exit ( 0 ) ;
273- } ) ;
264+ process . on ( 'SIGINT' , cleanup ) ;
265+ process . on ( 'SIGTERM' , cleanup ) ;
0 commit comments