Remove redundant comments and clean up code formatting

This commit is contained in:
Abdullah Sarwar
2025-12-07 21:37:36 +05:00
parent 1b1910bf80
commit 6a57bb9834
2 changed files with 14 additions and 230 deletions

View File

@ -8,8 +8,6 @@ import os
import subprocess
import json
import time
# Try to import matplotlib for graphing
try:
import matplotlib
matplotlib.use('Qt5Agg')
@ -18,39 +16,28 @@ try:
MATPLOTLIB_AVAILABLE = True
except ImportError:
MATPLOTLIB_AVAILABLE = False
# WhatsApp Web Thread (Node.js Bridge)
class WhatsAppWebThread(QThread):
"""Thread for interacting with WhatsApp Web Node.js bridge"""
log_signal = pyqtSignal(str, str)
qr_signal = pyqtSignal(str)
status_signal = pyqtSignal(bool)
spam_data_signal = pyqtSignal(dict) # Signal for spam timing data
spam_data_signal = pyqtSignal(dict)
def __init__(self, script_dir):
super().__init__()
self.script_dir = script_dir
self.process = None
self.running = False
def run(self):
self.running = True
# Bridge script is now in the same directory as this file
bridge_script = os.path.join(os.path.dirname(__file__), 'whatsapp_bridge.js')
# Debug: Print node version and path
try:
node_version = subprocess.check_output(['node', '--version'], text=True).strip()
node_path = subprocess.check_output(['which', 'node'], text=True).strip()
self.log_signal.emit(f"[*] Node.js found: {node_version} at {node_path}", "system")
except Exception as e:
self.log_signal.emit(f"[Error] Check node failed: {e}", "error")
self.log_signal.emit(f"[*] Starting bridge script: {bridge_script}", "system")
# Use absolute path for CWD
cwd = os.path.dirname(bridge_script)
self.process = subprocess.Popen(
['node', bridge_script],
stdin=subprocess.PIPE,
@ -58,12 +45,10 @@ class WhatsAppWebThread(QThread):
stderr=subprocess.PIPE,
text=True,
bufsize=1,
cwd=cwd # Run from the script's directory
cwd=cwd
)
# Check for immediate failure
try:
time.sleep(1) # Wait a bit to see if it crashes
time.sleep(1)
gone = self.process.poll()
if gone is not None:
stderr_output = self.process.stderr.read()
@ -72,7 +57,6 @@ class WhatsAppWebThread(QThread):
return
except Exception as e:
self.log_signal.emit(f"[Error] Monitor failed: {e}", "error")
while self.running and self.process.poll() is None:
line = self.process.stdout.readline()
if not line:
@ -83,14 +67,11 @@ class WhatsAppWebThread(QThread):
except json.JSONDecodeError:
if line.strip():
self.log_signal.emit(f"[Bridge] {line.strip()}", "system")
self.log_signal.emit("[*] WhatsApp Web client stopped.", "system")
self.status_signal.emit(False)
def _handle_bridge_message(self, data):
msg_type = data.get('type')
message = data.get('message', '')
if msg_type == 'qr':
self.log_signal.emit(f"[*] {message}", "system")
self.qr_signal.emit(data.get('data'))
@ -109,30 +90,27 @@ class WhatsAppWebThread(QThread):
is_ready = data.get('ready', False)
self.status_signal.emit(is_ready)
elif msg_type == 'ack':
# Message ACK status update
ack = data.get('ack', 0)
ack_name = data.get('ackName', 'UNKNOWN')
time_formatted = data.get('timeSinceSentFormatted', '?')
if ack == 1: # Single tick
if ack == 1:
self.log_signal.emit(f"[✓] Single tick after {time_formatted}", "system")
elif ack == 2: # Double tick
elif ack == 2:
self.log_signal.emit(f"[✓✓] Double tick after {time_formatted}", "success")
elif ack == 3: # Read
elif ack == 3:
self.log_signal.emit(f"[✓✓] Read (blue tick) after {time_formatted}", "success")
elif ack == 4: # Played
elif ack == 4:
self.log_signal.emit(f"[▶] Played after {time_formatted}", "success")
elif ack == -1: # Error
elif ack == -1:
self.log_signal.emit(f"[✗] Message error after {time_formatted}", "error")
else:
self.log_signal.emit(f"[*] ACK {ack_name} after {time_formatted}", "system")
elif msg_type == 'ack_timing':
# Timing between single and double tick
single_tick_ms = data.get('singleTickMs', 0)
double_tick_ms = data.get('doubleTickMs', 0)
single_to_double_ms = data.get('singleToDoubleMs', 0)
self.log_signal.emit(f"[⏱] Timing: Single tick @ {single_tick_ms}ms → Double tick @ {double_tick_ms}ms", "system")
self.log_signal.emit(f"[⏱] Single→Double: {single_to_double_ms}ms ({single_to_double_ms/1000:.2f}s)", "success")
# Emit timing data for graphing
self.spam_data_signal.emit({
'type': 'ack_timing',
'singleTickMs': single_tick_ms,
@ -150,7 +128,6 @@ class WhatsAppWebThread(QThread):
react_remove = data.get('reactionRemoveTimeMs', 0)
iteration_time = data.get('iterationTimeMs', 0)
self.log_signal.emit(f"[{index}/{total}] Send: {send_time}ms | React+: {react_add}ms | React-: {react_remove}ms | Total: {iteration_time}ms", "system")
# Emit data for graphing
self.spam_data_signal.emit({
'type': 'spam_iteration',
'index': index,
@ -164,7 +141,6 @@ class WhatsAppWebThread(QThread):
self.spam_data_signal.emit({'type': 'spam_complete'})
elif msg_type == 'spam_error':
self.log_signal.emit(f"[❌] {message}", "error")
def send_command(self, command):
if self.process and self.process.poll() is None:
try:
@ -174,7 +150,6 @@ class WhatsAppWebThread(QThread):
self.log_signal.emit(f"[Error] Failed to send command: {e}", "error")
else:
self.log_signal.emit("[Error] WhatsApp client is not running.", "error")
def stop(self):
self.running = False
if self.process:
@ -183,11 +158,8 @@ class WhatsAppWebThread(QThread):
self.process.wait(timeout=2)
except subprocess.TimeoutExpired:
self.process.kill()
class SilentWhispersWidget(QWidget):
"""Silent Whispers - Clean WhatsApp Reaction Spam Tool"""
def __init__(self, script_dir, parent=None):
super().__init__(parent)
self.script_dir = script_dir
@ -195,29 +167,19 @@ class SilentWhispersWidget(QWidget):
self.is_spamming = False
self.spam_data = {'indices': [], 'iteration_times': []}
self.init_ui()
def init_ui(self):
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(15, 15, 15, 15)
main_layout.setSpacing(10)
# Fonts matching main app
title_font = QFont()
title_font.setBold(True)
title_font.setPointSize(12)
subtitle_font = QFont()
subtitle_font.setPointSize(10)
# ═══════════════════════════════════════════════════════════
# TOP SECTION: Connection + Controls
# ═══════════════════════════════════════════════════════════
top_section = QHBoxLayout()
top_section.setSpacing(15)
# ── LEFT: QR Code Area ──
qr_container = QVBoxLayout()
qr_container.setSpacing(8)
self.qr_label = QLabel("Scan QR Code")
self.qr_label.setAlignment(Qt.AlignCenter)
self.qr_label.setFixedSize(160, 160)
@ -231,37 +193,25 @@ class SilentWhispersWidget(QWidget):
}
""")
qr_container.addWidget(self.qr_label)
# Connection button
self.wa_web_btn = QPushButton("Start Client")
self.wa_web_btn.setFont(subtitle_font)
self.wa_web_btn.setFixedWidth(160)
self.wa_web_btn.clicked.connect(self.toggle_whatsapp_client)
qr_container.addWidget(self.wa_web_btn)
# Status indicator
self.wa_web_status = QLabel("Disconnected")
self.wa_web_status.setAlignment(Qt.AlignCenter)
self.wa_web_status.setStyleSheet("color: #FF3B30; font-size: 10px;")
qr_container.addWidget(self.wa_web_status)
top_section.addLayout(qr_container)
# ── RIGHT: Controls ──
controls_container = QVBoxLayout()
controls_container.setSpacing(8)
# Phone input
phone_label = QLabel("PHONE NUMBER")
phone_label.setFont(subtitle_font)
controls_container.addWidget(phone_label)
self.phone_input = QLineEdit()
self.phone_input.setPlaceholderText("923001234567")
self.phone_input.setFont(subtitle_font)
controls_container.addWidget(self.phone_input)
# Delay input row
delay_row = QHBoxLayout()
delay_label = QLabel("DELAY")
delay_label.setFont(subtitle_font)
@ -274,11 +224,8 @@ class SilentWhispersWidget(QWidget):
delay_row.addWidget(delay_label)
delay_row.addWidget(self.spam_delay_input, 1)
controls_container.addLayout(delay_row)
# Action buttons
btn_row = QHBoxLayout()
btn_row.setSpacing(8)
self.spam_btn = QPushButton("START SPAM")
self.spam_btn.setFont(subtitle_font)
self.spam_btn.clicked.connect(self.start_reaction_spam)
@ -293,7 +240,6 @@ class SilentWhispersWidget(QWidget):
QPushButton:disabled { background-color: #1D1D1F; color: #555; }
""")
btn_row.addWidget(self.spam_btn)
self.stop_spam_btn = QPushButton("STOP SPAM")
self.stop_spam_btn.setFont(subtitle_font)
self.stop_spam_btn.clicked.connect(self.stop_reaction_spam)
@ -309,10 +255,7 @@ class SilentWhispersWidget(QWidget):
QPushButton:disabled { background-color: #1D1D1F; color: #555; }
""")
btn_row.addWidget(self.stop_spam_btn)
controls_container.addLayout(btn_row)
# Mini log
self.log_output = QTextEdit()
self.log_output.setReadOnly(True)
self.log_output.setMaximumHeight(60)
@ -327,13 +270,8 @@ class SilentWhispersWidget(QWidget):
}
""")
controls_container.addWidget(self.log_output)
top_section.addLayout(controls_container, 1)
main_layout.addLayout(top_section)
# ═══════════════════════════════════════════════════════════
# GRAPH SECTION: Full width
# ═══════════════════════════════════════════════════════════
if MATPLOTLIB_AVAILABLE:
self.figure = Figure(figsize=(8, 3), dpi=100, facecolor='#111113')
self.canvas = FigureCanvas(self.figure)
@ -341,8 +279,6 @@ class SilentWhispersWidget(QWidget):
self._style_graph()
self.figure.tight_layout(pad=2)
main_layout.addWidget(self.canvas, 1)
# Clear button
self.clear_graph_btn = QPushButton("Clear Graph")
self.clear_graph_btn.setFont(subtitle_font)
self.clear_graph_btn.clicked.connect(self.clear_graph)
@ -352,8 +288,6 @@ class SilentWhispersWidget(QWidget):
no_graph.setStyleSheet("color: #555;")
no_graph.setAlignment(Qt.AlignCenter)
main_layout.addWidget(no_graph)
# Hidden elements for compatibility
self.message_input = QTextEdit()
self.message_input.hide()
self.add_reaction_check = QComboBox()
@ -361,9 +295,7 @@ class SilentWhispersWidget(QWidget):
self.add_reaction_check.hide()
self.send_btn = QPushButton()
self.send_btn.hide()
self.log_message("[+] Ready", "success")
def _style_graph(self):
"""Apply dark theme to graph matching main app"""
self.ax.set_facecolor('#1D1D1F')
@ -375,7 +307,6 @@ class SilentWhispersWidget(QWidget):
for spine in self.ax.spines.values():
spine.set_color('#2a2a2e')
self.ax.grid(True, alpha=0.1, color='#444')
def log_message(self, message, msg_type="system"):
"""Add a message to the log output"""
color_map = {
@ -385,15 +316,9 @@ class SilentWhispersWidget(QWidget):
}
color = color_map.get(msg_type, "#e0e0e0")
self.log_output.append(f'<span style="color: {color};">{message}</span>')
# toggle_api_mode removed
def toggle_whatsapp_client(self):
"""Start or stop the WhatsApp Web client"""
if self.whatsapp_thread and isinstance(self.whatsapp_thread, WhatsAppWebThread) and self.whatsapp_thread.isRunning():
# Stop client
self.whatsapp_thread.stop()
self.whatsapp_thread.wait()
self.whatsapp_thread = None
@ -401,9 +326,8 @@ class SilentWhispersWidget(QWidget):
self.wa_web_btn.setText("Start WhatsApp Client")
self.wa_web_btn.setStyleSheet("")
self.qr_label.setText("Click Start to generate QR Code")
self.qr_label.setPixmap(QPixmap()) # Clear QR code
self.qr_label.setPixmap(QPixmap())
else:
# Start client
self.whatsapp_thread = WhatsAppWebThread(self.script_dir)
self.whatsapp_thread.log_signal.connect(self.log_message)
self.whatsapp_thread.qr_signal.connect(self.display_qr_code)
@ -413,34 +337,25 @@ class SilentWhispersWidget(QWidget):
self.wa_web_status.setText("Status: Starting...")
self.wa_web_btn.setText("Stop WhatsApp Client")
self.wa_web_btn.setStyleSheet("background-color: #d32f2f; color: white;")
def display_qr_code(self, qr_data):
"""Generate and display QR code from data"""
try:
import qrcode
from io import BytesIO
# Generate QR code image
qr = qrcode.QRCode(version=1, box_size=10, border=2)
qr.add_data(qr_data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# Convert to QPixmap using BytesIO (avoids ImageQt issues)
buffer = BytesIO()
img.save(buffer, format="PNG")
qim = QPixmap()
qim.loadFromData(buffer.getvalue(), "PNG")
# Scale to fit label
scaled_pixmap = qim.scaled(self.qr_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.qr_label.setPixmap(scaled_pixmap)
self.qr_label.setText("") # Clear text
self.qr_label.setText("")
except Exception as e:
self.log_message(f"Error generating QR: {e}", "error")
self.qr_label.setText(f"Error: {str(e)}")
def update_client_status(self, is_ready):
if is_ready:
self.wa_web_status.setText("Status: Connected & Ready")
@ -450,42 +365,29 @@ class SilentWhispersWidget(QWidget):
else:
self.wa_web_status.setText("Status: Disconnected")
self.wa_web_status.setStyleSheet("color: #f44336;")
def send_whatsapp_message(self):
"""Send WhatsApp message using Node.js bridge"""
phone_number = self.phone_input.text().strip()
message = self.message_input.toPlainText().strip()
add_reaction = self.add_reaction_check.currentIndex()
# Validate inputs
if not phone_number:
self.log_message("[-] Phone number is required!", "error")
return
if not message:
self.log_message("[-] Message cannot be empty!", "error")
return
# Check if client is running and ready
if not self.whatsapp_thread or not isinstance(self.whatsapp_thread, WhatsAppWebThread) or not self.whatsapp_thread.isRunning():
self.log_message("[-] WhatsApp Web client is not running!", "error")
self.log_message("[*] Click 'Start WhatsApp Client' and scan the QR code first", "system")
return
self.send_btn.setText("SENDING...")
self.send_btn.setEnabled(False)
# Clear previous logs
self.log_output.clear()
# Format phone number for WhatsApp Web.js
formatted_number = phone_number
if not formatted_number.endswith("@c.us") and not formatted_number.endswith("@g.us"):
formatted_number = f"{formatted_number}@c.us"
self.log_message(f"[*] Sending message to {formatted_number}", "system")
# Send command to Node.js bridge
if add_reaction == 1: # Add reaction
if add_reaction == 1:
self.whatsapp_thread.send_command({
"action": "sendMessageAndReact",
"chatId": formatted_number,
@ -498,16 +400,11 @@ class SilentWhispersWidget(QWidget):
"chatId": formatted_number,
"message": message
})
# Re-enable button
QTimer.singleShot(1000, lambda: self.send_btn.setEnabled(True))
QTimer.singleShot(1000, lambda: self.send_btn.setText("SEND MESSAGE"))
def on_send_finished(self, success, message):
"""Handle completion of WhatsApp send operation"""
# This might not be needed anymore as feedback comes via bridge logs
pass
def get_settings(self):
"""Get current settings for saving"""
return {
@ -515,7 +412,6 @@ class SilentWhispersWidget(QWidget):
'message': self.message_input.toPlainText(),
'add_reaction': self.add_reaction_check.currentIndex()
}
def load_settings(self, settings):
"""Load settings from saved configuration"""
if 'phone_number' in settings:
@ -524,119 +420,86 @@ class SilentWhispersWidget(QWidget):
self.message_input.setPlainText(settings['message'])
if 'add_reaction' in settings:
self.add_reaction_check.setCurrentIndex(settings['add_reaction'])
def toggle_reaction_spam(self):
"""Start or stop reaction spam"""
if self.is_spamming:
# Stop spam
self.stop_reaction_spam()
else:
# Start spam
self.start_reaction_spam()
def start_reaction_spam(self):
"""Start indefinite reaction spam on last message"""
phone_number = self.phone_input.text().strip()
delay = self.spam_delay_input.value()
# Validate inputs
if not phone_number:
self.log_message("[-] Phone number is required!", "error")
return
# Check if client is running and ready
if not self.whatsapp_thread or not isinstance(self.whatsapp_thread, WhatsAppWebThread) or not self.whatsapp_thread.isRunning():
self.log_message("[-] WhatsApp Web client is not running!", "error")
self.log_message("[*] Click 'Start Client' and scan QR code first", "system")
return
self.is_spamming = True
self.spam_btn.setText("SPAMMING...")
self.spam_btn.setEnabled(False)
self.stop_spam_btn.setEnabled(True)
# Clear previous logs and graph data
self.log_output.clear()
self.clear_graph()
# Format phone number for WhatsApp Web.js
formatted_number = phone_number
if not formatted_number.endswith("@c.us") and not formatted_number.endswith("@g.us"):
formatted_number = f"{formatted_number}@c.us"
self.log_message(f"[*] Starting reaction spam on {formatted_number}", "system")
self.log_message(f"[*] Delay: {delay}ms", "system")
# Send spam command to Node.js bridge
self.whatsapp_thread.send_command({
"action": "startReactionSpam",
"chatId": formatted_number,
"delayMs": delay,
"emoji": "thumbsup"
})
def stop_reaction_spam(self):
"""Stop the reaction spam"""
if self.whatsapp_thread and isinstance(self.whatsapp_thread, WhatsAppWebThread) and self.whatsapp_thread.isRunning():
self.whatsapp_thread.send_command({
"action": "stopReactionSpam"
})
self.is_spamming = False
self.spam_btn.setText("START SPAM")
self.spam_btn.setEnabled(True)
self.spam_btn.setStyleSheet("background-color: #34C759; color: white;")
self.stop_spam_btn.setEnabled(False)
self.send_btn.setEnabled(True)
def handle_spam_data(self, data):
"""Handle spam data for graphing"""
data_type = data.get('type')
if data_type == 'spam_start':
# Reset data for new spam session
self.spam_data = {
'indices': [],
'iteration_times': []
}
elif data_type == 'spam_iteration':
# Add iteration data - only track iteration time (purple line)
self.spam_data['indices'].append(data.get('index', 0))
self.spam_data['iteration_times'].append(data.get('iterationTimeMs', 0))
self.update_graph()
elif data_type == 'spam_stopped' or data_type == 'spam_stopping':
# Spam was stopped
self.is_spamming = False
self.spam_btn.setText("START SPAM")
self.spam_btn.setEnabled(True)
self.stop_spam_btn.setEnabled(False)
def update_graph(self):
"""Update the matplotlib graph with current data"""
if not MATPLOTLIB_AVAILABLE:
return
self.ax.clear()
self._style_graph()
indices = self.spam_data['indices']
if indices and self.spam_data['iteration_times']:
# Plot small dots only
self.ax.scatter(indices, self.spam_data['iteration_times'],
c='#FF6B9D', s=6, alpha=0.9, edgecolors='none', linewidths=0)
self.canvas.draw()
def clear_graph(self):
"""Clear the graph and reset data"""
self.spam_data = {
'indices': [],
'iteration_times': []
}
if MATPLOTLIB_AVAILABLE:
self.ax.clear()
self._style_graph()

View File

@ -1,6 +1,5 @@
const { Client, LocalAuth, MessageAck } = require('whatsapp-web.js');
const qrcode = require('qrcode-terminal');
const client = new Client({
authStrategy: new LocalAuth({
dataPath: './.wwebjs_auth'
@ -10,14 +9,9 @@ const client = new Client({
args: ['--no-sandbox', '--disable-setuid-sandbox']
}
});
let isReady = false;
let isSpamming = false;
// Track messages for ACK timing
const messageTracking = new Map();
// ACK status names for logging
const ackStatusNames = {
[-1]: 'ERROR',
[0]: 'PENDING (clock)',
@ -26,21 +20,15 @@ const ackStatusNames = {
[3]: 'READ (blue tick)',
[4]: 'PLAYED'
};
// Listen for message ACK updates
client.on('message_ack', (message, ack) => {
const messageId = message.id._serialized;
const tracking = messageTracking.get(messageId);
if (tracking) {
const now = Date.now();
const timeSinceSent = now - tracking.sentAt;
const ackName = ackStatusNames[ack] || `UNKNOWN(${ack})`;
// Store timing for each ACK level
if (!tracking.ackTimes) tracking.ackTimes = {};
tracking.ackTimes[ack] = timeSinceSent;
console.log(JSON.stringify({
type: 'ack',
messageId: messageId,
@ -50,8 +38,6 @@ client.on('message_ack', (message, ack) => {
timeSinceSentFormatted: formatTime(timeSinceSent),
message: `Message ${ackName} after ${formatTime(timeSinceSent)}`
}));
// Calculate time between single and double tick
if (ack === 2 && tracking.ackTimes[1]) {
const singleToDouble = tracking.ackTimes[2] - tracking.ackTimes[1];
console.log(JSON.stringify({
@ -63,22 +49,17 @@ client.on('message_ack', (message, ack) => {
message: `Single→Double tick: ${formatTime(singleToDouble)}`
}));
}
// Clean up tracking after read/played (or after 5 minutes)
if (ack >= 3) {
messageTracking.delete(messageId);
}
}
});
function formatTime(ms) {
if (ms < 1000) return `${ms}ms`;
if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;
return `${(ms / 60000).toFixed(2)}min`;
}
client.on('qr', (qr) => {
// Send QR code to Python via stdout
qrcode.generate(qr, { small: true });
console.log(JSON.stringify({
type: 'qr',
@ -86,7 +67,6 @@ client.on('qr', (qr) => {
message: 'Scan the QR code above with WhatsApp on your phone'
}));
});
client.on('ready', () => {
isReady = true;
console.log(JSON.stringify({
@ -94,21 +74,18 @@ client.on('ready', () => {
message: 'WhatsApp Web client is ready!'
}));
});
client.on('authenticated', () => {
console.log(JSON.stringify({
type: 'authenticated',
message: 'Authentication successful'
}));
});
client.on('auth_failure', (msg) => {
console.log(JSON.stringify({
type: 'error',
message: `Authentication failed: ${msg}`
}));
});
client.on('disconnected', (reason) => {
console.log(JSON.stringify({
type: 'disconnected',
@ -116,12 +93,9 @@ client.on('disconnected', (reason) => {
}));
isReady = false;
});
// Listen for commands from Python via stdin
process.stdin.on('data', async (data) => {
try {
const cmd = JSON.parse(data.toString().trim());
if (!isReady && cmd.action !== 'status') {
console.log(JSON.stringify({
type: 'error',
@ -130,30 +104,24 @@ process.stdin.on('data', async (data) => {
}));
return;
}
if (cmd.action === 'status') {
console.log(JSON.stringify({
type: 'status',
ready: isReady
}));
}
else if (cmd.action === 'sendMessage') {
const chatId = cmd.chatId;
const message = cmd.message;
const chat = await client.getChatById(chatId);
const sentAt = Date.now();
const sentMsg = await chat.sendMessage(message);
const messageId = sentMsg.id._serialized;
// Track this message for ACK timing
messageTracking.set(messageId, {
sentAt: sentAt,
chatId: chatId,
type: 'message'
});
console.log(JSON.stringify({
type: 'success',
action: 'sendMessage',
@ -162,15 +130,12 @@ process.stdin.on('data', async (data) => {
message: 'Message sent successfully - tracking ACK status...'
}));
}
else if (cmd.action === 'addReaction') {
const chatId = cmd.chatId;
const emoji = cmd.emoji || '👍';
const messageIndex = cmd.messageIndex || 0; // 0 = last message
const messageIndex = cmd.messageIndex || 0;
const chat = await client.getChatById(chatId);
const messages = await chat.fetchMessages({ limit: messageIndex + 1 });
if (messages.length > messageIndex) {
await messages[messageIndex].react(emoji);
console.log(JSON.stringify({
@ -186,37 +151,24 @@ process.stdin.on('data', async (data) => {
}));
}
}
else if (cmd.action === 'sendMessageAndReact') {
const chatId = cmd.chatId;
const message = cmd.message;
const emoji = cmd.emoji || '👍';
// Send the message first
const chat = await client.getChatById(chatId);
const sentAt = Date.now();
const sentMsg = await chat.sendMessage(message);
const messageId = sentMsg.id._serialized;
// Track this message for ACK timing
messageTracking.set(messageId, {
sentAt: sentAt,
chatId: chatId,
type: 'message'
});
// Wait a moment for the message to be registered
await new Promise(resolve => setTimeout(resolve, 500));
// Get the last message (which should be from the other person)
const messages = await chat.fetchMessages({ limit: 2 });
// React to the second-to-last message (skip the one we just sent)
if (messages.length >= 2) {
const reactionSentAt = Date.now();
await messages[1].react(emoji);
// Track reaction timing (reactions don't have ACK but we track when sent)
console.log(JSON.stringify({
type: 'success',
action: 'sendMessageAndReact',
@ -233,19 +185,13 @@ process.stdin.on('data', async (data) => {
}));
}
}
else if (cmd.action === 'startReactionSpam') {
const chatId = cmd.chatId;
const delayMs = cmd.delayMs || 100;
const emoji = cmd.emoji || '👍';
// Stop any existing spam
isSpamming = false;
await new Promise(resolve => setTimeout(resolve, 100));
const chat = await client.getChatById(chatId);
// Get the last message to react to
const messages = await chat.fetchMessages({ limit: 1 });
if (messages.length === 0) {
console.log(JSON.stringify({
@ -255,10 +201,8 @@ process.stdin.on('data', async (data) => {
}));
return;
}
const targetMessage = messages[0];
const targetMessageId = targetMessage.id._serialized;
console.log(JSON.stringify({
type: 'spam_start',
action: 'startReactionSpam',
@ -266,32 +210,21 @@ process.stdin.on('data', async (data) => {
delayMs: delayMs,
message: `Starting reaction spam on message, ${delayMs}ms delay...`
}));
isSpamming = true;
let iteration = 0;
while (isSpamming) {
try {
iteration++;
const iterationStart = Date.now();
// Add reaction
const reactionAddStart = Date.now();
await targetMessage.react(emoji);
const reactionAddTime = Date.now() - reactionAddStart;
// Wait before removing
await new Promise(resolve => setTimeout(resolve, delayMs));
if (!isSpamming) break;
// Remove reaction
const reactionRemoveStart = Date.now();
await targetMessage.react('');
const reactionRemoveTime = Date.now() - reactionRemoveStart;
const iterationTime = Date.now() - iterationStart;
console.log(JSON.stringify({
type: 'spam_iteration',
index: iteration,
@ -300,10 +233,7 @@ process.stdin.on('data', async (data) => {
iterationTimeMs: iterationTime,
message: `[${iteration}] React+: ${reactionAddTime}ms, React-: ${reactionRemoveTime}ms, Total: ${iterationTime}ms`
}));
// Delay before next iteration
await new Promise(resolve => setTimeout(resolve, delayMs));
} catch (iterError) {
console.log(JSON.stringify({
type: 'spam_error',
@ -311,11 +241,9 @@ process.stdin.on('data', async (data) => {
error: iterError.message,
message: `Error on iteration ${iteration}: ${iterError.message}`
}));
// Small delay before retry
await new Promise(resolve => setTimeout(resolve, 500));
}
}
console.log(JSON.stringify({
type: 'spam_stopped',
action: 'startReactionSpam',
@ -323,7 +251,6 @@ process.stdin.on('data', async (data) => {
message: `Reaction spam stopped after ${iteration} iterations.`
}));
}
else if (cmd.action === 'stopReactionSpam') {
isSpamming = false;
console.log(JSON.stringify({
@ -332,14 +259,12 @@ process.stdin.on('data', async (data) => {
message: 'Stopping reaction spam...'
}));
}
else {
console.log(JSON.stringify({
type: 'error',
message: `Unknown action: ${cmd.action}`
}));
}
} catch (error) {
console.log(JSON.stringify({
type: 'error',
@ -348,8 +273,6 @@ process.stdin.on('data', async (data) => {
}));
}
});
// Handle process termination
process.on('SIGINT', async () => {
console.log(JSON.stringify({
type: 'info',
@ -358,8 +281,6 @@ process.on('SIGINT', async () => {
await client.destroy();
process.exit(0);
});
// Initialize the client
console.log(JSON.stringify({
type: 'info',
message: 'Initializing WhatsApp Web client...'