GistifyDeveloper Gists
Explore Code
New Snippet

The minimalist platform to share code snippets, manage gists, and customize your developer identity.

Platform

  • New Snippet
  • Explore Gists
  • Profile Dashboard

Legal & Privacy

  • Privacy Policy
  • Terms of Service
  • Cookie Policy

Developer

  • GitHub @kyiov
  • Telegram Support
  • Saweria
© 2026 GistifyAll rights reserved.

Crafted with by kyio

Back to Home
Gistify Snippet
kyiov
@kyiov
3h ago·1 file(s)
View profile
Public

WhatsApp Channel Reaction Sender (ChannelReact)

Automated WhatsApp Channel emoji reaction sender with session handshake, Cloudflare Chrome fingerprint impersonation, retry backoff, and VIP reaction task queuing.

#whatsapp#reaction#python#channel#automation#tools
17
Raw
channelreact.pypython
174 lines
1#!/usr/bin/env python3
2"""
3ChannelReact — WhatsApp Channel reaction sender (pure Python).
4
5Sends emoji reactions to a WhatsApp Channel post via the public worker
6satriareact.satriadeveloperz.workers.dev. Uses curl_cffi with Chrome
7impersonation because the worker mis-routes non-browser fingerprints
8(undici / httpx / curl) to an upstream 502.
9
10Flow (mirrors the site):
11 1. POST /api/handshake -> token (valid ~60s).
12 2. POST /api/react {url, reactions, token} -> queued to VIP API backend.
13
14Usage:
15 python3 channelreact.py <channelUrl> <emoji> [more emoji...]
16 python3 channelreact.py <channelUrl> -n 3 --delay 1500 👍 ❤️
17
18Dependency: pip install curl_cffi
19"""
20
21import sys
22import time
23import re
24import json
25
26from curl_cffi import requests
27
28BASE = "https://satriareact.satriadeveloperz.workers.dev"
29UA = (
30 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
31 "(KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
32)
33
34EMOJI_RE = re.compile(r"[\U0001F300-\U0001FAFF\u2600-\u27BF\uFE0F]", re.UNICODE)
35TEXT_OK = {
36 "santuy", "sad", "nice", "siap", "ok", "good", "wow", "keren",
37 "marempu", "mantap", "ganteng", "cantik", "anjay", "mantul", "limit",
38 "by", "tenkyu", "dengerin", "sell", "gajelas", "kepo", "jelek",
39}
40
41
42def is_emoji(s):
43 v = str(s or "").strip()
44 if not v:
45 return False
46 return len(v) <= 8 and (bool(EMOJI_RE.search(v)) or v.lower() in TEXT_OK)
47
48
49class ChannelReact:
50 def __init__(self, session=None):
51 self.session = session or self._new_session()
52
53 @staticmethod
54 def _new_session():
55 s = requests.Session(impersonate="chrome")
56 s.headers.update({
57 "User-Agent": UA,
58 "Origin": BASE,
59 "Referer": BASE + "/",
60 })
61 return s
62
63 def handshake(self, retries=8):
64 """Handshake sampai dapat token. Token TERIKAT ke session (cookies)
65 — session yang berhasil harus dipakai untuk react."""
66 last = None
67 for i in range(retries + 1):
68 if i > 0:
69 # Backoff: 3s, 6s, 9s ... plus fresh session (upstream flakes).
70 time.sleep(3 * i)
71 self.session = self._new_session()
72 try:
73 r = self.session.post(BASE + "/api/handshake", json={}, timeout=30)
74 data = r.json() if r.headers.get("content-type", "").startswith("application/json") else None
75 if r.status_code == 200 and data and data.get("success"):
76 return {
77 "token": data["token"],
78 "clientId": data.get("clientId"),
79 "username": (data.get("step1") or {}).get("username") or data.get("username"),
80 "expiresInMs": data.get("expiresInMs", 60000),
81 }
82 last = "HTTP %s: %s" % (r.status_code, str(data)[:120])
83 except Exception as e:
84 last = str(e)
85 raise RuntimeError("Handshake gagal (%s)" % last)
86
87 def react(self, url, reactions, token=None, retries=3):
88 """Kirim reaksi memakai session yang sama dengan handshake (token terikat
89 session). 401 invalid_handshake / 403 / 502 -> handshake ulang & retry."""
90 tok = token
91 if tok is None:
92 tok = self.handshake()["token"] # self.session ikut ter-set
93 for attempt in range(1, retries + 1):
94 try:
95 r = self.session.post(
96 BASE + "/api/react",
97 json={"url": url, "reactions": reactions, "token": tok},
98 timeout=45,
99 )
100 except Exception:
101 self.session = self._new_session()
102 tok = self.handshake()["token"]
103 continue
104 data = r.json() if r.headers.get("content-type", "").startswith("application/json") else None
105 if r.status_code == 200 and data and data.get("success"):
106 return {
107 "success": True,
108 "count": data.get("count", len(reactions)),
109 "message": data.get("message"),
110 "task": (data.get("task") or {}).get("status"),
111 "vip": (data.get("vip") or {}).get("packageName"),
112 }
113 # Token invalid / expired -> handshake ulang pada session baru, retry.
114 if r.status_code in (401, 403) and attempt < retries:
115 time.sleep(2.0 * attempt)
116 self.session = self._new_session()
117 tok = self.handshake()["token"]
118 continue
119 if r.status_code == 502 and attempt < retries:
120 time.sleep(2.5 * attempt)
121 self.session = self._new_session()
122 tok = self.handshake()["token"]
123 continue
124 raise RuntimeError("Reaksi gagal (HTTP %s): %s" % (r.status_code, str(data)[:140]))
125 raise RuntimeError("Reaksi gagal setelah %d percobaan" % retries)
126
127
128def main():
129 args = sys.argv[1:]
130 url = next((a for a in args if not a.startswith("-")), None)
131 n = 1
132 delay = 1000
133 if "-n" in args:
134 n = int(args[args.index("-n") + 1])
135 if "--delay" in args:
136 delay = int(args[args.index("--delay") + 1])
137 reactions = [a for a in args if a != url and not a.startswith("-") and not a.isdigit()]
138
139 if not url:
140 print("Usage: python3 satriareact.py <channelUrl> <emoji> [more...]")
141 print(" python3 satriareact.py <channelUrl> -n 3 --delay 1500 👍 ❤️")
142 return 1
143
144 if not reactions:
145 reactions = ["santuy"]
146 for r in reactions:
147 if not is_emoji(r):
148 print("ERROR: `%s` bukan emoji — gunakan emoji seperti 👍 ❤️ 🎉" % r)
149 return 1
150
151 print("== ChannelReact (channel reaction sender) ==")
152 print("url : %s" % url)
153 print("react : [%s] x %d" % (", ".join(reactions), n))
154
155 try:
156 client = ChannelReact()
157 token = client.handshake()["token"]
158 print("handshake: ok")
159 total = 0
160 for i in range(n):
161 res = client.react(url, reactions, token)
162 total += res["count"]
163 print(" #%d: ok (count %s, task %s)" % (i + 1, res["count"], res["task"]))
164 if i < n - 1 and delay > 0:
165 time.sleep(delay / 1000.0)
166 print("done : %d reaction(s) dikirim" % total)
167 return 0
168 except Exception as e:
169 print("ERROR: %s" % e)
170 return 1
171
172
173if __name__ == "__main__":
174 sys.exit(main())
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.