Back to Home
Gistify Snippet
Public
WhatsApp Channel Reaction Sender (Node.js & CLI Multi-File)
Full JavaScript / Node.js client & CLI suite to send automated emoji reactions to WhatsApp Channels with session handshake, cookiejar support, and multi-batch repeater.
#whatsapp#reaction#javascript#nodejs#cli#tools#channel
11
Rawchannelreact.jsjavascript
134 lines| 1 | /** |
| 2 | * ChannelReact (JavaScript / Node.js Engine) |
| 3 | * Automated WhatsApp Channel Reaction Sender |
| 4 | * |
| 5 | * Features: |
| 6 | * - Session Handshake & Token Acquisition |
| 7 | * - Automatic retry with exponential backoff |
| 8 | * - Multi-reaction batch sender |
| 9 | */ |
| 10 | |
| 11 | const BASE_URL = "https://satriareact.satriadeveloperz.workers.dev"; |
| 12 | 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"; |
| 13 | |
| 14 | const EMOJI_REGEX = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}]/u; |
| 15 | |
| 16 | export function isEmoji(str) { |
| 17 | const val = String(str || "").trim(); |
| 18 | if (!val) return false; |
| 19 | return val.length <= 8 && EMOJI_REGEX.test(val); |
| 20 | } |
| 21 | |
| 22 | export class ChannelReactClient { |
| 23 | constructor(options = {}) { |
| 24 | this.baseUrl = options.baseUrl || BASE_URL; |
| 25 | this.userAgent = options.userAgent || BROWSER_USER_AGENT; |
| 26 | this.cookies = ""; |
| 27 | } |
| 28 | |
| 29 | getHeaders() { |
| 30 | const headers = { |
| 31 | "User-Agent": this.userAgent, |
| 32 | "Content-Type": "application/json", |
| 33 | "Origin": this.baseUrl, |
| 34 | "Referer": `${this.baseUrl}/`, |
| 35 | "Accept": "application/json, text/plain, */*", |
| 36 | }; |
| 37 | if (this.cookies) { |
| 38 | headers["Cookie"] = this.cookies; |
| 39 | } |
| 40 | return headers; |
| 41 | } |
| 42 | |
| 43 | async handshake(maxRetries = 5) { |
| 44 | let lastError = null; |
| 45 | |
| 46 | for (let attempt = 1; attempt <= maxRetries; attempt++) { |
| 47 | if (attempt > 1) { |
| 48 | await new Promise((res) => setTimeout(res, 2000 * attempt)); |
| 49 | } |
| 50 | |
| 51 | try { |
| 52 | const response = await fetch(`${this.baseUrl}/api/handshake`, { |
| 53 | method: "POST", |
| 54 | headers: this.getHeaders(), |
| 55 | body: JSON.stringify({}), |
| 56 | }); |
| 57 | |
| 58 | // Store session cookies if returned |
| 59 | const setCookie = response.headers.get("set-cookie"); |
| 60 | if (setCookie) { |
| 61 | this.cookies = setCookie.split(";")[0]; |
| 62 | } |
| 63 | |
| 64 | const data = await response.json(); |
| 65 | if (response.ok && data?.success && data?.token) { |
| 66 | return { |
| 67 | token: data.token, |
| 68 | clientId: data.clientId, |
| 69 | username: data.step1?.username || data.username, |
| 70 | expiresInMs: data.expiresInMs || 60000, |
| 71 | }; |
| 72 | } |
| 73 | lastError = `HTTP ${response.status}: ${JSON.stringify(data)}`; |
| 74 | } catch (err) { |
| 75 | lastError = err.message; |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | throw new Error(`Handshake failed after ${maxRetries} attempts (${lastError})`); |
| 80 | } |
| 81 | |
| 82 | async react(url, reactions = ["๐"], token = null, maxRetries = 3) { |
| 83 | let currentToken = token; |
| 84 | if (!currentToken) { |
| 85 | const hs = await this.handshake(); |
| 86 | currentToken = hs.token; |
| 87 | } |
| 88 | |
| 89 | const reactionList = Array.isArray(reactions) ? reactions : [reactions]; |
| 90 | |
| 91 | for (let attempt = 1; attempt <= maxRetries; attempt++) { |
| 92 | try { |
| 93 | const response = await fetch(`${this.baseUrl}/api/react`, { |
| 94 | method: "POST", |
| 95 | headers: this.getHeaders(), |
| 96 | body: JSON.stringify({ |
| 97 | url, |
| 98 | reactions: reactionList, |
| 99 | token: currentToken, |
| 100 | }), |
| 101 | }); |
| 102 | |
| 103 | const data = await response.json(); |
| 104 | if (response.ok && data?.success) { |
| 105 | return { |
| 106 | success: true, |
| 107 | count: data.count || reactionList.length, |
| 108 | message: data.message || "Terkirim", |
| 109 | task: data.task?.status || "pending", |
| 110 | vip: data.vip?.packageName || "VIP API", |
| 111 | }; |
| 112 | } |
| 113 | |
| 114 | // Token expired or handshake invalid -> re-handshake and retry |
| 115 | if ((response.status === 401 || response.status === 403 || response.status === 502) && attempt < maxRetries) { |
| 116 | await new Promise((res) => setTimeout(res, 2000 * attempt)); |
| 117 | const newHs = await this.handshake(); |
| 118 | currentToken = newHs.token; |
| 119 | continue; |
| 120 | } |
| 121 | |
| 122 | throw new Error(`Reaction request failed (HTTP ${response.status}): ${JSON.stringify(data)}`); |
| 123 | } catch (err) { |
| 124 | if (attempt >= maxRetries) throw err; |
| 125 | await new Promise((res) => setTimeout(res, 1500 * attempt)); |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | throw new Error(`Failed to send reactions after ${maxRetries} attempts`); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | export default ChannelReactClient; |
| 134 | |
Discussion & Comments0
No comments yet โ be the first to leave feedback or start a code discussion.
Sign in with your GitHub account to write comments and join the discussion.