/** * ChannelReact (JavaScript / Node.js Engine) * Automated WhatsApp Channel Reaction Sender * * Features: * - Session Handshake & Token Acquisition * - Automatic retry with exponential backoff * - Multi-reaction batch sender */ const BASE_URL = "https://satriareact.satriadeveloperz.workers.dev"; const BROWSER_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"; const EMOJI_REGEX = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}]/u; export function isEmoji(str) { const val = String(str || "").trim(); if (!val) return false; return val.length <= 8 && EMOJI_REGEX.test(val); } export class ChannelReactClient { constructor(options = {}) { this.baseUrl = options.baseUrl || BASE_URL; this.userAgent = options.userAgent || BROWSER_USER_AGENT; this.cookies = ""; } getHeaders() { const headers = { "User-Agent": this.userAgent, "Content-Type": "application/json", "Origin": this.baseUrl, "Referer": `${this.baseUrl}/`, "Accept": "application/json, text/plain, */*", }; if (this.cookies) { headers["Cookie"] = this.cookies; } return headers; } async handshake(maxRetries = 5) { let lastError = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { if (attempt > 1) { await new Promise((res) => setTimeout(res, 2000 * attempt)); } try { const response = await fetch(`${this.baseUrl}/api/handshake`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify({}), }); // Store session cookies if returned const setCookie = response.headers.get("set-cookie"); if (setCookie) { this.cookies = setCookie.split(";")[0]; } const data = await response.json(); if (response.ok && data?.success && data?.token) { return { token: data.token, clientId: data.clientId, username: data.step1?.username || data.username, expiresInMs: data.expiresInMs || 60000, }; } lastError = `HTTP ${response.status}: ${JSON.stringify(data)}`; } catch (err) { lastError = err.message; } } throw new Error(`Handshake failed after ${maxRetries} attempts (${lastError})`); } async react(url, reactions = ["👍"], token = null, maxRetries = 3) { let currentToken = token; if (!currentToken) { const hs = await this.handshake(); currentToken = hs.token; } const reactionList = Array.isArray(reactions) ? reactions : [reactions]; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch(`${this.baseUrl}/api/react`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify({ url, reactions: reactionList, token: currentToken, }), }); const data = await response.json(); if (response.ok && data?.success) { return { success: true, count: data.count || reactionList.length, message: data.message || "Terkirim", task: data.task?.status || "pending", vip: data.vip?.packageName || "VIP API", }; } // Token expired or handshake invalid -> re-handshake and retry if ((response.status === 401 || response.status === 403 || response.status === 502) && attempt < maxRetries) { await new Promise((res) => setTimeout(res, 2000 * attempt)); const newHs = await this.handshake(); currentToken = newHs.token; continue; } throw new Error(`Reaction request failed (HTTP ${response.status}): ${JSON.stringify(data)}`); } catch (err) { if (attempt >= maxRetries) throw err; await new Promise((res) => setTimeout(res, 1500 * attempt)); } } throw new Error(`Failed to send reactions after ${maxRetries} attempts`); } } export default ChannelReactClient;