Fourstore commited on
Commit
c0bfa7f
Β·
1 Parent(s): d156a6a
Files changed (4) hide show
  1. Dockerfile +14 -0
  2. README.md +1 -2
  3. app.py +856 -262
  4. requirement.txt +4 -3
Dockerfile ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ COPY . .
10
+
11
+ ENV PORT=7860
12
+ EXPOSE 7860
13
+
14
+ CMD ["python", "app.py"]=
README.md CHANGED
@@ -3,9 +3,8 @@ title: SMS OTP Bot
3
  emoji: πŸ€–
4
  colorFrom: blue
5
  colorTo: green
6
- sdk: gradio
7
  sdk_version: 4.31.0
8
- app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
  short_description: Bot Telegram untuk forward OTP dari panel SMS
 
3
  emoji: πŸ€–
4
  colorFrom: blue
5
  colorTo: green
6
+ sdk: docker
7
  sdk_version: 4.31.0
 
8
  pinned: false
9
  license: apache-2.0
10
  short_description: Bot Telegram untuk forward OTP dari panel SMS
app.py CHANGED
@@ -1,318 +1,912 @@
1
  import httpx
2
  from bs4 import BeautifulSoup
3
  import re
4
- from datetime import datetime, timedelta
5
  import time
6
- import threading
7
- import gradio as gr
 
 
 
8
 
9
- # ================= CONFIG =================
10
  BASE = "http://159.69.3.189"
11
  LOGIN_URL = f"{BASE}/login"
12
  GET_RANGE_URL = f"{BASE}/portal/sms/received/getsms"
13
  GET_NUMBER_URL = f"{BASE}/portal/sms/received/getsms/number"
14
  GET_SMS_URL = f"{BASE}/portal/sms/received/getsms/number/sms"
15
 
16
- USERNAME = "rezaciledug35@gmail.com"
17
- PASSWORD = "Ciledug577"
 
 
 
18
 
19
- BOT_TOKEN = "8458437657:AAGtvRh5dBdLIhONXN3rH1heDrgCSW-jyHQ"
20
- CHAT_ID = "-1003328735276"
21
-
22
- session = httpx.Client(follow_redirects=True, timeout=30)
23
  sent_cache = set()
24
  csrf_token = None
25
- bot_running = False
26
- bot_thread = None
27
- log_messages = []
28
- # ==========================================
 
 
 
 
 
 
29
 
30
- # ================= DATE LOGIC =================
31
  def get_search_date():
32
- now = datetime.now()
33
- if now.hour < 7:
34
- yesterday = now - timedelta(days=1)
35
- return yesterday.strftime("%Y-%m-%d")
36
- else:
37
- return now.strftime("%Y-%m-%d")
38
 
39
- # ================= TELEGRAM =================
40
- def tg_send(msg):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  try:
42
- url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
43
- session.post(url, data={
44
- "chat_id": CHAT_ID,
45
- "text": msg,
46
- "parse_mode": "Markdown"
47
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  except Exception as e:
49
- add_log(f"[TELEGRAM ERROR] {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- # ================= UTIL =================
52
  def extract_otp(text):
53
- m = re.search(r"\b(\d{3}[- ]?\d{3}|\d{4,6})\b", text)
54
- return m.group(0) if m else None
 
 
 
 
 
55
 
56
- def clean_country(rng: str) -> str:
57
- return re.sub(r"\s*\d+$", "", rng).strip()
58
 
59
- def mask_number(number: str) -> str:
 
60
  clean = re.sub(r"[^\d+]", "", number)
61
- if len(clean) <= 6:
62
- return clean
63
- start = clean[:5]
64
- end = clean[-3:]
65
- masked = "*" * (len(clean) - len(start) - len(end))
66
- return f"{start}{masked}{end}"
67
-
68
- # ================= SERVICE MAPPING =================
69
- SERVICE_MAPPING = {
70
- "WHATSAPP": ["whatsapp", "wa", "whatsapp business"],
71
- "TELEGRAM": ["telegram"],
72
- "GOOGLE": ["google", "gmail"],
73
- "FACEBOOK": ["facebook", "fb"],
74
- "INSTAGRAM": ["instagram", "ig"],
75
- "TIKTOK": ["tiktok"],
76
- "TWITTER": ["twitter", "x"],
77
- "DISCORD": ["discord"],
78
- "AMAZON": ["amazon", "aws"],
79
- }
80
 
81
- def map_service(raw_service: str) -> str:
82
- if not raw_service:
83
- return "UNKNOWN"
84
- s = raw_service.lower().strip()
85
- for service, keywords in SERVICE_MAPPING.items():
86
- for k in keywords:
87
- if k in s:
88
- return service
89
- return raw_service.upper()
 
90
 
91
- # ================= LOGIN =================
92
- def login():
93
- global csrf_token
94
  try:
95
- r = session.get(LOGIN_URL)
96
- soup = BeautifulSoup(r.text, "html.parser")
97
- csrf_token = soup.find("input", {"name": "_token"})["value"]
98
- session.post(LOGIN_URL, data={
99
- "_token": csrf_token,
100
- "email": USERNAME,
101
- "password": PASSWORD
102
- })
103
- add_log("[LOGIN] BERHASIL")
104
- tg_send("βœ… BOT AKTIF & LOGIN BERHASIL")
105
  return True
106
- except Exception as e:
107
- add_log(f"[LOGIN ERROR] {e}")
108
  return False
109
 
110
- # ================= GET RANGE =================
111
- def get_ranges():
112
- date = get_search_date()
113
-
114
- r = session.post(GET_RANGE_URL, data={
115
- "_token": csrf_token,
116
- "from": date,
117
- "to": date
118
- })
119
-
120
- soup = BeautifulSoup(r.text, "html.parser")
121
- ranges = []
122
- for div in soup.find_all("div", onclick=True):
123
- if "getDetials" in div["onclick"]:
124
- ranges.append(div["onclick"].split("'")[1])
125
- return ranges
126
 
127
- # ================= GET NUMBERS =================
128
- def get_numbers(rng):
129
- date = get_search_date()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
- r = session.post(GET_NUMBER_URL, data={
132
- "_token": csrf_token,
133
- "start": date,
134
- "end": date,
135
- "range": rng
136
- })
137
 
138
- soup = BeautifulSoup(r.text, "html.parser")
139
- numbers = []
140
- for div in soup.find_all("div", onclick=True):
141
- if "getDetialsNumber" in div["onclick"]:
142
- numbers.append(div["onclick"].split("'")[1])
143
- return numbers
144
-
145
- # ================= GET SMS =================
146
- def get_sms(rng, number):
147
- date = get_search_date()
148
 
149
- r = session.post(GET_SMS_URL, data={
150
- "_token": csrf_token,
151
- "start": date,
152
- "end": date,
153
- "Number": number,
154
- "Range": rng
155
- })
156
 
157
- soup = BeautifulSoup(r.text, "html.parser")
158
- results = []
159
 
160
- for card in soup.select("div.card.card-body"):
161
- service = "UNKNOWN"
162
- service_div = card.select_one("div.col-sm-4")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
- if service_div:
165
- span = service_div.find("span")
166
- raw = service_div.get_text(strip=True)
167
- if span:
168
- raw = raw.replace(span.get_text(strip=True), "").strip()
169
- service = map_service(raw)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
- msg_p = card.find("p")
172
- sms = msg_p.get_text(strip=True) if msg_p else ""
 
 
 
173
 
174
- results.append((service, sms))
175
-
176
- return results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
- # ================= LOGGING =================
179
- def add_log(msg):
180
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
181
- log_entry = f"[{timestamp}] {msg}"
182
- log_messages.append(log_entry)
183
- if len(log_messages) > 100:
184
- log_messages.pop(0)
185
- print(log_entry)
186
 
187
- def get_logs():
188
- return "\n".join(log_messages)
189
 
190
- # ================= BOT LOOP =================
191
- def bot_worker():
192
- global bot_running, csrf_token, sent_cache
 
 
 
193
 
194
- add_log("πŸ€– Memulai bot...")
 
 
 
195
 
196
- if not login():
197
- add_log("❌ Login gagal, bot berhenti")
198
- bot_running = False
199
- return
200
 
201
- while bot_running:
202
  try:
203
- ranges = get_ranges()
204
- add_log(f"πŸ“Š Mendapatkan {len(ranges)} range")
 
 
205
 
206
- for rng in ranges:
207
- if not bot_running:
208
- break
209
-
210
- country = clean_country(rng)
211
- numbers = get_numbers(rng)
 
 
 
 
 
 
 
 
212
 
213
- for num in numbers:
214
- if not bot_running:
215
- break
216
-
217
- masked_num = mask_number(num)
218
 
219
- for service, sms in get_sms(rng, num):
220
- otp = extract_otp(sms)
221
- sms_id = f"{rng}-{num}-{sms[:50]}"
222
-
223
- if sms_id in sent_cache:
224
- continue
 
 
225
 
226
- if otp:
227
- msg = (
228
- f"πŸ”” *NEW OTP RECEIVED!*\n\n"
229
- f"🌍 *Country* : {country}\n"
230
- f"πŸ“ž *Number* : {masked_num}\n"
231
- f"πŸ’¬ *Service* : {service}\n"
232
- f"πŸ” *OTP Code* : `{otp}`\n\n"
233
- f"βœ‰οΈ *Message* :\n"
234
- f"```\n{sms[:200]}\n```"
235
- )
236
 
237
- tg_send(msg)
238
- sent_cache.add(sms_id)
239
- add_log(f"βœ… SENT | {country} | {masked_num} | {service} | {otp}")
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
- time.sleep(5)
 
242
 
243
  except Exception as e:
244
- add_log(f"[ERROR] {e}")
245
- time.sleep(5)
246
-
247
- add_log("πŸ›‘ Bot berhenti")
248
 
249
- # ================= BOT CONTROL =================
250
- def start_bot():
251
- global bot_running, bot_thread, sent_cache
252
-
253
- if bot_running:
254
- return "⚠️ Bot sudah berjalan!"
255
-
256
- bot_running = True
257
- sent_cache = set()
258
- bot_thread = threading.Thread(target=bot_worker, daemon=True)
259
- bot_thread.start()
260
-
261
- return "βœ… Bot dimulai!"
262
-
263
- def stop_bot():
264
- global bot_running
265
-
266
- if not bot_running:
267
- return "⚠️ Bot tidak berjalan!"
268
-
269
- bot_running = False
270
- add_log("⏹️ Menghentikan bot...")
271
-
272
- return "πŸ›‘ Bot dihentikan!"
273
-
274
- def clear_cache():
275
- global sent_cache
276
- count = len(sent_cache)
277
- sent_cache.clear()
278
- return f"🧹 Cache dibersihkan! {count} entry dihapus."
279
-
280
- def get_status():
281
- status = "🟒 BERJALAN" if bot_running else "πŸ”΄ BERHENTI"
282
- return f"Status Bot: {status}\nCache Size: {len(sent_cache)}"
283
-
284
- # ================= GRADIO INTERFACE =================
285
- with gr.Blocks(title="SMS OTP Bot", theme=gr.themes.Soft()) as demo:
286
- gr.Markdown("# πŸ€– SMS OTP Telegram Bot")
287
- gr.Markdown(f"Bot akan mengirim OTP ke Telegram Channel")
288
-
289
- with gr.Row():
290
- with gr.Column(scale=1):
291
- start_btn = gr.Button("▢️ Start Bot", variant="primary")
292
- stop_btn = gr.Button("⏹️ Stop Bot", variant="stop")
293
- clear_btn = gr.Button("🧹 Clear Cache")
294
- status_btn = gr.Button("πŸ“Š Status")
295
-
296
- with gr.Column(scale=2):
297
- status_output = gr.Textbox(label="Status", value="πŸ”΄ BERHENTI", lines=2)
298
- log_output = gr.Textbox(label="Log Activity", value="", lines=15, max_lines=20)
299
-
300
- # Events
301
- start_btn.click(start_bot, outputs=status_output).then(
302
- lambda: get_logs(), outputs=log_output
303
- )
304
-
305
- stop_btn.click(stop_bot, outputs=status_output).then(
306
- lambda: get_logs(), outputs=log_output
307
- )
308
-
309
- clear_btn.click(clear_cache, outputs=status_output)
310
-
311
- status_btn.click(get_status, outputs=status_output)
312
-
313
- # Auto refresh logs
314
- demo.load(lambda: get_logs(), outputs=log_output, every=2)
315
-
316
- # ================= MAIN =================
317
  if __name__ == "__main__":
318
- demo.launch()
 
 
 
 
1
  import httpx
2
  from bs4 import BeautifulSoup
3
  import re
4
+ from datetime import datetime, timedelta, timezone
5
  import time
6
+ from flask import Flask, Response, request
7
+ from threading import Thread
8
+ from collections import deque
9
+ import json
10
+ import os
11
 
 
12
  BASE = "http://159.69.3.189"
13
  LOGIN_URL = f"{BASE}/login"
14
  GET_RANGE_URL = f"{BASE}/portal/sms/received/getsms"
15
  GET_NUMBER_URL = f"{BASE}/portal/sms/received/getsms/number"
16
  GET_SMS_URL = f"{BASE}/portal/sms/received/getsms/number/sms"
17
 
18
+ USERNAME = os.getenv("HF_USERNAME", "rezaciledug35@gmail.com")
19
+ PASSWORD = os.getenv("HF_PASSWORD", "Ciledug577")
20
+ BOT_TOKEN = os.getenv("BOT_TOKEN", "8458437657:AAGtvRh5dBdLIhONXN3rH1heDrgCSW-jyHQ")
21
+ CHAT_ID = os.getenv("CHAT_ID", "-1003328735276")
22
+ CUSTOM_DOMAIN = os.getenv("CUSTOM_DOMAIN", "https://fourstore-tele.hf.space")
23
 
24
+ session = httpx.Client(follow_redirects=True, timeout=30.0)
 
 
 
25
  sent_cache = set()
26
  csrf_token = None
27
+ LOGIN_SUCCESS = False
28
+
29
+ sms_cache = {}
30
+ sms_counter = {}
31
+ range_counter = {}
32
+ otp_logs = deque(maxlen=100)
33
+ sse_clients = []
34
+
35
+ def get_wib_time():
36
+ return datetime.now(timezone.utc) + timedelta(hours=7)
37
 
 
38
  def get_search_date():
39
+ wib = get_wib_time()
40
+ return (wib - timedelta(days=1)).strftime("%Y-%m-%d") if wib.hour < 7 else wib.strftime("%Y-%m-%d")
 
 
 
 
41
 
42
+ def login():
43
+ global csrf_token, LOGIN_SUCCESS
44
+ try:
45
+ print("πŸ” Login...")
46
+ r = session.get(LOGIN_URL, timeout=30)
47
+ soup = BeautifulSoup(r.text, "html.parser")
48
+ token = soup.find("input", {"name": "_token"})
49
+ if not token:
50
+ print("❌ Token tidak ditemukan!")
51
+ return False
52
+ csrf_token = token.get("value")
53
+
54
+ r = session.post(LOGIN_URL, data={
55
+ "_token": csrf_token,
56
+ "email": USERNAME,
57
+ "password": PASSWORD
58
+ }, timeout=30)
59
+
60
+ if "dashboard" in r.text.lower() or "logout" in r.text.lower():
61
+ LOGIN_SUCCESS = True
62
+ print("βœ… Login BERHASIL!")
63
+ return True
64
+ else:
65
+ LOGIN_SUCCESS = False
66
+ print("❌ Login GAGAL!")
67
+ return False
68
+ except Exception as e:
69
+ LOGIN_SUCCESS = False
70
+ print(f"❌ Login Error: {e}")
71
+ return False
72
+
73
+ def get_ranges_with_count():
74
+ try:
75
+ date = get_search_date()
76
+ r = session.post(GET_RANGE_URL, data={
77
+ "_token": csrf_token,
78
+ "from": date,
79
+ "to": date
80
+ }, timeout=15)
81
+
82
+ soup = BeautifulSoup(r.text, "html.parser")
83
+ ranges_data = []
84
+
85
+ for item in soup.select(".item"):
86
+ name_div = item.select_one(".col-sm-4")
87
+ if not name_div: continue
88
+ rng = name_div.get_text(strip=True)
89
+
90
+ count_p = item.select_one(".col-3 .mb-0.pb-0")
91
+ count = int(count_p.get_text(strip=True)) if count_p else 0
92
+
93
+ ranges_data.append({
94
+ "name": rng,
95
+ "count": count
96
+ })
97
+
98
+ return ranges_data
99
+ except Exception as e:
100
+ return []
101
+
102
+ def get_numbers_with_count(rng):
103
  try:
104
+ date = get_search_date()
105
+ r = session.post(GET_NUMBER_URL, data={
106
+ "_token": csrf_token,
107
+ "start": date,
108
+ "end": date,
109
+ "range": rng
110
+ }, timeout=15)
111
+
112
+ soup = BeautifulSoup(r.text, "html.parser")
113
+ numbers_data = []
114
+
115
+ for div in soup.find_all("div", onclick=True):
116
+ onclick = div.get("onclick", "")
117
+ if "getDetialsNumber" in onclick:
118
+ num = div.get_text(strip=True)
119
+ if num and num.isdigit() and len(num) > 5:
120
+ parent = div.find_parent("div", class_="card")
121
+ count = 0
122
+ if parent:
123
+ p_tag = parent.find("p", class_="mb-0 pb-0")
124
+ if p_tag:
125
+ count = int(p_tag.get_text(strip=True))
126
+
127
+ numbers_data.append({
128
+ "number": num,
129
+ "count": count
130
+ })
131
+
132
+ return numbers_data
133
  except Exception as e:
134
+ return []
135
+
136
+ def get_sms_fast(rng, number):
137
+ try:
138
+ date = get_search_date()
139
+ cache_key = f"{rng}-{number}"
140
+
141
+ if cache_key in sms_cache:
142
+ timestamp, results = sms_cache[cache_key]
143
+ if time.time() - timestamp < 5:
144
+ return results
145
+
146
+ parts = rng.split()
147
+ range_name = parts[0] + " " + parts[1] if len(parts) >= 2 else rng
148
+
149
+ r = session.post(GET_SMS_URL, data={
150
+ "_token": csrf_token,
151
+ "start": date,
152
+ "end": date,
153
+ "Number": number,
154
+ "Range": range_name
155
+ }, timeout=20)
156
+
157
+ soup = BeautifulSoup(r.text, "html.parser")
158
+ results = []
159
+
160
+ for card in soup.select("div.card.card-body"):
161
+ try:
162
+ service = "UNKNOWN"
163
+ service_div = card.select_one("div.col-sm-4")
164
+ if service_div:
165
+ raw = service_div.get_text(strip=True)
166
+ service = map_service(raw)
167
+
168
+ msg_p = card.find("p", class_="mb-0 pb-0")
169
+ if msg_p:
170
+ sms = msg_p.get_text(strip=True)
171
+ otp = extract_otp(sms)
172
+ if otp:
173
+ results.append((service, sms, otp))
174
+ except:
175
+ continue
176
+
177
+ sms_cache[cache_key] = (time.time(), results)
178
+ return results
179
+ except:
180
+ return []
181
 
 
182
  def extract_otp(text):
183
+ if not text: return None
184
+ m = re.search(r"\b(\d{4,6})\b", text)
185
+ if not m:
186
+ m = re.search(r"\b(\d{3}[- ]?\d{3})\b", text)
187
+ if m:
188
+ return m.group(0).replace("-", "").replace(" ", "")
189
+ return None
190
 
191
+ def clean_country(rng):
192
+ return re.sub(r"\s*\d+$", "", rng).strip() if rng else "UNKNOWN"
193
 
194
+ def mask_number(number):
195
+ if not number: return "UNKNOWN"
196
  clean = re.sub(r"[^\d+]", "", number)
197
+ if len(clean) <= 6: return clean
198
+ return f"{clean[:4]}****{clean[-3:]}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
+ def map_service(raw):
201
+ if not raw: return "UNKNOWN"
202
+ s = raw.lower().strip()
203
+ if 'whatsapp' in s: return "WHATSAPP"
204
+ if 'telegram' in s: return "TELEGRAM"
205
+ if 'google' in s or 'gmail' in s: return "GOOGLE"
206
+ if 'facebook' in s or 'fb' in s: return "FACEBOOK"
207
+ if 'instagram' in s or 'ig' in s: return "INSTAGRAM"
208
+ if 'tiktok' in s: return "TIKTOK"
209
+ return raw.upper()
210
 
211
+ def tg_send(msg):
 
 
212
  try:
213
+ httpx.post(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
214
+ data={"chat_id": CHAT_ID, "text": msg, "parse_mode": "Markdown"},
215
+ timeout=10)
 
 
 
 
 
 
 
216
  return True
217
+ except:
 
218
  return False
219
 
220
+ def add_otp_log(country, number, service, otp, sms):
221
+ wib = get_wib_time()
222
+ log_entry = {
223
+ "time": wib.strftime("%H:%M:%S"),
224
+ "country": country,
225
+ "number": number,
226
+ "service": service,
227
+ "otp": otp,
228
+ "sms": sms[:80] + "..." if len(sms) > 80 else sms
229
+ }
230
+ otp_logs.appendleft(log_entry)
231
+ broadcast_sse(log_entry)
232
+ return log_entry
 
 
 
233
 
234
+ def broadcast_sse(data):
235
+ msg = f"data: {json.dumps(data)}\n\n"
236
+ dead = []
237
+ for q in sse_clients:
238
+ try:
239
+ q.put(msg)
240
+ except:
241
+ dead.append(q)
242
+ for q in dead:
243
+ sse_clients.remove(q)
244
+
245
+ app = Flask('')
246
+
247
+ @app.route('/')
248
+ def home():
249
+ wib = get_wib_time()
250
+ search_date = get_search_date()
251
 
252
+ search_query = request.args.get('q', '').lower()
253
+ filter_service = request.args.get('service', '')
 
 
 
 
254
 
255
+ filtered_logs = list(otp_logs)
256
+ if search_query:
257
+ filtered_logs = [log for log in filtered_logs if
258
+ search_query in log['country'].lower() or
259
+ search_query in log['number'].lower() or
260
+ search_query in log['otp'].lower() or
261
+ search_query in log['sms'].lower()]
 
 
 
262
 
263
+ if filter_service:
264
+ filtered_logs = [log for log in filtered_logs if log['service'] == filter_service]
 
 
 
 
 
265
 
266
+ all_services = list(set([log['service'] for log in otp_logs]))
 
267
 
268
+ html = f"""
269
+ <!DOCTYPE html>
270
+ <html lang="en">
271
+ <head>
272
+ <meta charset="UTF-8">
273
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
274
+ <title>OTP DASHBOARD Β· SEARCH</title>
275
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
276
+ <style>
277
+ * {{
278
+ margin: 0;
279
+ padding: 0;
280
+ box-sizing: border-box;
281
+ }}
282
+
283
+ body {{
284
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
285
+ background: #0a0c10;
286
+ color: #e4e6eb;
287
+ padding: 24px;
288
+ }}
289
+
290
+ .container {{
291
+ max-width: 1400px;
292
+ margin: 0 auto;
293
+ }}
294
+
295
+ .header {{
296
+ background: linear-gradient(145deg, #1a1f2c, #0f131c);
297
+ border-radius: 24px;
298
+ padding: 28px;
299
+ margin-bottom: 28px;
300
+ border: 1px solid #2d3540;
301
+ box-shadow: 0 8px 20px rgba(0,0,0,0.4);
302
+ }}
303
+
304
+ .header-top {{
305
+ display: flex;
306
+ justify-content: space-between;
307
+ align-items: center;
308
+ margin-bottom: 24px;
309
+ }}
310
+
311
+ .title h1 {{
312
+ font-size: 28px;
313
+ font-weight: 700;
314
+ background: linear-gradient(135deg, #00f2fe, #4facfe);
315
+ -webkit-background-clip: text;
316
+ -webkit-text-fill-color: transparent;
317
+ margin-bottom: 6px;
318
+ }}
319
+
320
+ .title p {{
321
+ color: #8b949e;
322
+ font-size: 14px;
323
+ }}
324
+
325
+ .status-badge {{
326
+ padding: 10px 24px;
327
+ border-radius: 100px;
328
+ font-weight: 600;
329
+ font-size: 14px;
330
+ background: {'#0a4d3c' if LOGIN_SUCCESS else '#4a2c2c'};
331
+ color: {'#a0f0d0' if LOGIN_SUCCESS else '#ffb3b3'};
332
+ border: 1px solid {'#1a6e5a' if LOGIN_SUCCESS else '#6e3a3a'};
333
+ }}
334
+
335
+ .stats-grid {{
336
+ display: grid;
337
+ grid-template-columns: repeat(4, 1fr);
338
+ gap: 20px;
339
+ margin-top: 20px;
340
+ }}
341
+
342
+ .stat-card {{
343
+ background: #1a1f2c;
344
+ padding: 20px;
345
+ border-radius: 20px;
346
+ border: 1px solid #2d3540;
347
+ }}
348
+
349
+ .stat-label {{
350
+ color: #8b949e;
351
+ font-size: 13px;
352
+ text-transform: uppercase;
353
+ letter-spacing: 0.5px;
354
+ margin-bottom: 8px;
355
+ }}
356
+
357
+ .stat-value {{
358
+ font-size: 32px;
359
+ font-weight: 700;
360
+ color: #00f2fe;
361
+ }}
362
+
363
+ .search-section {{
364
+ background: #0f131c;
365
+ border-radius: 20px;
366
+ padding: 20px;
367
+ margin-bottom: 24px;
368
+ border: 1px solid #2d3540;
369
+ display: flex;
370
+ gap: 16px;
371
+ align-items: center;
372
+ flex-wrap: wrap;
373
+ }}
374
+
375
+ .search-box {{
376
+ flex: 2;
377
+ min-width: 280px;
378
+ position: relative;
379
+ }}
380
+
381
+ .search-icon {{
382
+ position: absolute;
383
+ left: 16px;
384
+ top: 14px;
385
+ color: #8b949e;
386
+ }}
387
+
388
+ .search-input {{
389
+ width: 100%;
390
+ padding: 14px 20px 14px 48px;
391
+ background: #1a1f2c;
392
+ border: 1px solid #2d3540;
393
+ border-radius: 100px;
394
+ color: #e4e6eb;
395
+ font-size: 15px;
396
+ transition: all 0.2s;
397
+ }}
398
+
399
+ .search-input:focus {{
400
+ outline: none;
401
+ border-color: #00f2fe;
402
+ background: #1e2433;
403
+ box-shadow: 0 0 0 3px #00f2fe20;
404
+ }}
405
+
406
+ .filter-box {{
407
+ flex: 1;
408
+ min-width: 180px;
409
+ }}
410
+
411
+ .filter-select {{
412
+ width: 100%;
413
+ padding: 14px 20px;
414
+ background: #1a1f2c;
415
+ border: 1px solid #2d3540;
416
+ border-radius: 100px;
417
+ color: #e4e6eb;
418
+ font-size: 15px;
419
+ cursor: pointer;
420
+ }}
421
+
422
+ .filter-select:focus {{
423
+ outline: none;
424
+ border-color: #00f2fe;
425
+ }}
426
+
427
+ .clear-btn {{
428
+ padding: 14px 28px;
429
+ background: #2d3a4a;
430
+ border: none;
431
+ border-radius: 100px;
432
+ color: white;
433
+ font-size: 14px;
434
+ font-weight: 600;
435
+ cursor: pointer;
436
+ transition: all 0.2s;
437
+ text-decoration: none;
438
+ display: inline-block;
439
+ }}
440
+
441
+ .clear-btn:hover {{
442
+ background: #3d4a5a;
443
+ }}
444
+
445
+ .result-count {{
446
+ color: #8b949e;
447
+ font-size: 13px;
448
+ margin-left: 8px;
449
+ }}
450
+
451
+ .otp-section {{
452
+ background: #0f131c;
453
+ border-radius: 24px;
454
+ padding: 28px;
455
+ border: 1px solid #2d3540;
456
+ }}
457
+
458
+ .section-title {{
459
+ font-size: 18px;
460
+ font-weight: 600;
461
+ color: #e4e6eb;
462
+ margin-bottom: 20px;
463
+ display: flex;
464
+ align-items: center;
465
+ gap: 10px;
466
+ }}
467
+
468
+ .section-title span {{
469
+ background: #00f2fe20;
470
+ padding: 6px 12px;
471
+ border-radius: 100px;
472
+ font-size: 12px;
473
+ color: #00f2fe;
474
+ }}
475
+
476
+ table {{
477
+ width: 100%;
478
+ border-collapse: collapse;
479
+ }}
480
+
481
+ th {{
482
+ text-align: left;
483
+ padding: 16px 12px;
484
+ background: #1a1f2c;
485
+ color: #00f2fe;
486
+ font-weight: 600;
487
+ font-size: 13px;
488
+ text-transform: uppercase;
489
+ letter-spacing: 0.5px;
490
+ border-bottom: 2px solid #2d3540;
491
+ }}
492
+
493
+ td {{
494
+ padding: 16px 12px;
495
+ border-bottom: 1px solid #262c38;
496
+ font-size: 14px;
497
+ }}
498
+
499
+ .otp-badge {{
500
+ background: #002b36;
501
+ color: #00f2fe;
502
+ font-family: 'Monaco', 'Menlo', monospace;
503
+ font-size: 16px;
504
+ font-weight: 700;
505
+ padding: 6px 14px;
506
+ border-radius: 100px;
507
+ border: 1px solid #00f2fe40;
508
+ display: inline-block;
509
+ user-select: all;
510
+ -webkit-user-select: all;
511
+ -moz-user-select: all;
512
+ cursor: pointer;
513
+ transition: all 0.2s;
514
+ }}
515
+
516
+ .otp-badge:hover {{
517
+ background: #003d4a;
518
+ border-color: #00f2fe;
519
+ transform: scale(1.05);
520
+ }}
521
+
522
+ .service-badge {{
523
+ background: #2d3a4a;
524
+ padding: 6px 14px;
525
+ border-radius: 100px;
526
+ font-size: 12px;
527
+ font-weight: 600;
528
+ color: #e4e6eb;
529
+ display: inline-block;
530
+ }}
531
+
532
+ .service-badge.whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
533
+ .service-badge.telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
534
+ .service-badge.google {{ background: #EA433520; color: #EA4335; border: 1px solid #EA433540; }}
535
+ .service-badge.facebook {{ background: #4267B220; color: #4267B2; border: 1px solid #4267B240; }}
536
+ .service-badge.instagram {{ background: #E4405F20; color: #E4405F; border: 1px solid #E4405F40; }}
537
+ .service-badge.tiktok {{ background: #00000020; color: #ffffff; border: 1px solid #ffffff40; }}
538
+
539
+ .number {{
540
+ font-family: 'Monaco', 'Menlo', monospace;
541
+ color: #e4e6eb;
542
+ }}
543
+
544
+ .empty-state {{
545
+ text-align: center;
546
+ padding: 60px;
547
+ color: #8b949e;
548
+ }}
549
+
550
+ .empty-state h3 {{
551
+ font-size: 20px;
552
+ margin-bottom: 12px;
553
+ color: #b1bac4;
554
+ }}
555
+
556
+ .highlight {{
557
+ background: #00f2fe30;
558
+ border-radius: 4px;
559
+ padding: 0 2px;
560
+ }}
561
+
562
+ @keyframes fadeIn {{
563
+ from {{ opacity: 0; transform: translateY(-10px); }}
564
+ to {{ opacity: 1; transform: translateY(0); }}
565
+ }}
566
+
567
+ .new-row {{
568
+ animation: fadeIn 0.3s ease;
569
+ background: linear-gradient(90deg, #00f2fe10, transparent);
570
+ }}
571
+
572
+ .toast {{
573
+ position: fixed;
574
+ bottom: 24px;
575
+ right: 24px;
576
+ background: #00f2fe;
577
+ color: #000;
578
+ padding: 14px 28px;
579
+ border-radius: 100px;
580
+ font-weight: 600;
581
+ animation: slideIn 0.3s ease;
582
+ z-index: 9999;
583
+ box-shadow: 0 8px 20px rgba(0,242,254,0.3);
584
+ }}
585
+
586
+ @keyframes slideIn {{
587
+ from {{ opacity: 0; transform: translateX(30px); }}
588
+ to {{ opacity: 1; transform: translateX(0); }}
589
+ }}
590
+ </style>
591
+ </head>
592
+ <body>
593
+ <div class="container">
594
+ <div class="header">
595
+ <div class="header-top">
596
+ <div class="title">
597
+ <h1>OTP MONITOR Β· SEARCH</h1>
598
+ <p>{CUSTOM_DOMAIN}</p>
599
+ </div>
600
+ <div class="status-badge">
601
+ {'● ONLINE' if LOGIN_SUCCESS else 'β—‹ OFFLINE'}
602
+ </div>
603
+ </div>
604
+
605
+ <div class="stats-grid">
606
+ <div class="stat-card">
607
+ <div class="stat-label">Total OTP</div>
608
+ <div class="stat-value" id="total-otp">{len(sent_cache)}</div>
609
+ </div>
610
+ <div class="stat-card">
611
+ <div class="stat-label">Today</div>
612
+ <div class="stat-value" id="today-otp">{len(otp_logs)}</div>
613
+ </div>
614
+ <div class="stat-card">
615
+ <div class="stat-label">WIB</div>
616
+ <div class="stat-value" id="wib-time">{wib.strftime('%H:%M:%S')}</div>
617
+ </div>
618
+ <div class="stat-card">
619
+ <div class="stat-label">Date</div>
620
+ <div class="stat-value">{search_date}</div>
621
+ </div>
622
+ </div>
623
+ </div>
624
+
625
+ <div class="search-section">
626
+ <div class="search-box">
627
+ <span class="search-icon">πŸ”</span>
628
+ <form action="/" method="get" style="margin: 0;" id="searchForm">
629
+ <input type="text"
630
+ class="search-input"
631
+ name="q"
632
+ placeholder="Cari negara, nomor, OTP, atau SMS..."
633
+ value="{request.args.get('q', '')}"
634
+ autocomplete="off">
635
+ </form>
636
+ </div>
637
+
638
+ <div class="filter-box">
639
+ <select class="filter-select" name="service" onchange="window.location.href='/?' + (document.getElementById('searchForm').q.value ? 'q=' + document.getElementById('searchForm').q.value + '&' : '') + 'service=' + this.value">
640
+ <option value="">πŸ“‹ Semua Service</option>
641
+ {''.join([f'<option value="{s}" {"selected" if filter_service == s else ""}>πŸ“± {s}</option>' for s in sorted(all_services)])}
642
+ </select>
643
+ </div>
644
+
645
+ <a href="/" class="clear-btn">βœ• Clear</a>
646
+
647
+ <span class="result-count">πŸ“Š {len(filtered_logs)} results</span>
648
+ </div>
649
+
650
+ <div class="otp-section">
651
+ <div class="section-title">
652
+ πŸ“¨ OTP TERBARU
653
+ <span>LIVE</span>
654
+ </div>
655
+
656
+ <div style="overflow-x: auto;">
657
+ <table>
658
+ <thead>
659
+ <tr>
660
+ <th>WIB</th>
661
+ <th>Country</th>
662
+ <th>Number</th>
663
+ <th>Service</th>
664
+ <th>OTP</th>
665
+ <th>Message</th>
666
+ </tr>
667
+ </thead>
668
+ <tbody id="otp-table-body">
669
+ {generate_filtered_rows(filtered_logs, search_query)}
670
+ </tbody>
671
+ </table>
672
+ </div>
673
+ </div>
674
+ </div>
675
 
676
+ <script>
677
+ let eventSource;
678
+
679
+ function connectSSE() {{
680
+ eventSource = new EventSource('/stream');
681
+
682
+ eventSource.onmessage = function(e) {{
683
+ try {{
684
+ const data = JSON.parse(e.data);
685
+ if (data.otp) {{
686
+ addRow(data);
687
+ updateStats();
688
+ }}
689
+ }} catch (err) {{
690
+ console.error('SSE Error:', err);
691
+ }}
692
+ }};
693
+
694
+ eventSource.onerror = function() {{
695
+ setTimeout(connectSSE, 3000);
696
+ }};
697
+ }}
698
+
699
+ function addRow(data) {{
700
+ const tbody = document.getElementById('otp-table-body');
701
+
702
+ if (tbody.children.length === 1 && tbody.children[0].innerHTML.includes('Belum ada OTP')) {{
703
+ tbody.innerHTML = '';
704
+ }}
705
+
706
+ const row = tbody.insertRow(0);
707
+ row.className = 'new-row';
708
+
709
+ const serviceClass = data.service.toLowerCase().replace(' ', '');
710
+
711
+ row.innerHTML = `
712
+ <td style="color: #00f2fe;">${{data.time}}</td>
713
+ <td>${{data.country}}</td>
714
+ <td><span class="number">${{data.number}}</span></td>
715
+ <td><span class="service-badge ${{serviceClass}}">${{data.service}}</span></td>
716
+ <td><span class="otp-badge" onclick="copyOTP('${{data.otp}}')">${{data.otp}}</span></td>
717
+ <td><div style="max-width: 350px; overflow: hidden; text-overflow: ellipsis;" title="${{data.sms}}">${{data.sms}}</div></td>
718
+ `;
719
+
720
+ while (tbody.children.length > 50) {{
721
+ tbody.removeChild(tbody.lastChild);
722
+ }}
723
+ }}
724
+
725
+ function copyOTP(otp) {{
726
+ navigator.clipboard.writeText(otp).then(() => {{
727
+ const toast = document.createElement('div');
728
+ toast.className = 'toast';
729
+ toast.textContent = 'βœ… OTP copied!';
730
+ document.body.appendChild(toast);
731
+ setTimeout(() => toast.remove(), 2000);
732
+ }});
733
+ }}
734
+
735
+ function updateStats() {{
736
+ const totalEl = document.getElementById('total-otp');
737
+ const todayEl = document.getElementById('today-otp');
738
+
739
+ if (totalEl) {{
740
+ totalEl.textContent = parseInt(totalEl.textContent || 0) + 1;
741
+ }}
742
+ if (todayEl) {{
743
+ todayEl.textContent = parseInt(todayEl.textContent || 0) + 1;
744
+ }}
745
+ }}
746
+
747
+ function updateTime() {{
748
+ const now = new Date();
749
+ now.setHours(now.getHours() + 7);
750
+ const timeStr = now.toISOString().substr(11, 8);
751
+ const wibEl = document.getElementById('wib-time');
752
+ if (wibEl) wibEl.textContent = timeStr;
753
+ }}
754
+
755
+ document.querySelector('.search-input')?.addEventListener('keypress', function(e) {{
756
+ if (e.key === 'Enter') {{
757
+ document.getElementById('searchForm').submit();
758
+ }}
759
+ }});
760
+
761
+ connectSSE();
762
+ setInterval(updateTime, 1000);
763
+ </script>
764
+ </body>
765
+ </html>
766
+ """
767
+ return html
768
+
769
+ def generate_filtered_rows(filtered_logs, search_query=""):
770
+ if not filtered_logs:
771
+ return '''
772
+ <tr>
773
+ <td colspan="6" class="empty-state">
774
+ <h3>πŸ” Tidak ada hasil</h3>
775
+ <p>Coba kata kunci lain atau reset filter</p>
776
+ </td>
777
+ </tr>
778
+ '''
779
+
780
+ rows = ""
781
+ for entry in filtered_logs[:50]:
782
+ country = entry["country"]
783
+ number = entry["number"]
784
+ otp = entry["otp"]
785
+ sms = entry["sms"]
786
 
787
+ if search_query:
788
+ country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', country, flags=re.I)
789
+ number = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', number, flags=re.I)
790
+ otp = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', otp, flags=re.I)
791
+ sms = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', sms, flags=re.I)
792
 
793
+ service_class = entry["service"].lower().replace(' ', '')
794
+
795
+ rows += f'''
796
+ <tr>
797
+ <td style="color: #00f2fe;">{entry["time"]}</td>
798
+ <td>{country}</td>
799
+ <td><span class="number">{number}</span></td>
800
+ <td><span class="service-badge {service_class}">{entry["service"]}</span></td>
801
+ <td><span class="otp-badge" onclick="copyOTP('{entry["otp"]}')">{otp}</span></td>
802
+ <td><div style="max-width: 350px; overflow: hidden; text-overflow: ellipsis;" title="{entry["sms"]}">{sms}</div></td>
803
+ </tr>
804
+ '''
805
+ return rows
806
+
807
+ @app.route('/stream')
808
+ def stream():
809
+ def generate():
810
+ q = Queue()
811
+ sse_clients.append(q)
812
+ try:
813
+ while True:
814
+ yield q.get()
815
+ except:
816
+ if q in sse_clients:
817
+ sse_clients.remove(q)
818
+ return Response(generate(), mimetype="text/event-stream")
819
 
820
+ from queue import Queue
821
+ def run_server():
822
+ app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
 
 
 
 
 
823
 
824
+ Thread(target=run_server, daemon=True).start()
 
825
 
826
+ def main():
827
+ print("\n" + "="*60)
828
+ print(" πŸ”₯ OTP BOT - RANGE FOCUS MODE")
829
+ print(" ⚑ HANYA FOKUS KE RANGE YANG BERUBAH")
830
+ print(" πŸ” DASHBOARD DENGAN SEARCH & FILTER")
831
+ print("="*60 + "\n")
832
 
833
+ for i in range(5):
834
+ if login():
835
+ break
836
+ time.sleep(3)
837
 
838
+ last_cleanup = time.time()
 
 
 
839
 
840
+ while True:
841
  try:
842
+ if not LOGIN_SUCCESS or not csrf_token:
843
+ login()
844
+ time.sleep(2)
845
+ continue
846
 
847
+ if time.time() - last_cleanup > 300:
848
+ sms_cache.clear()
849
+ sms_counter.clear()
850
+ range_counter.clear()
851
+ print("🧹 Cache cleared")
852
+ last_cleanup = time.time()
853
+
854
+ ranges_data = get_ranges_with_count()
855
+ print(f"\nπŸ“‘ Scan {len(ranges_data)} ranges...")
856
+
857
+ for range_item in ranges_data:
858
+ rng = range_item["name"]
859
+ current_count = range_item["count"]
860
+ prev_count = range_counter.get(rng, 0)
861
 
862
+ if current_count > prev_count:
863
+ country = clean_country(rng)
864
+ print(f"\nπŸ”₯ RANGE BERUBAH: {country}")
865
+ print(f" πŸ“Š {prev_count} β†’ {current_count} SMS")
866
+ range_counter[rng] = current_count
867
 
868
+ numbers_data = get_numbers_with_count(rng)
869
+ print(f" πŸ“ž Scan {len(numbers_data)} nomor...")
870
+
871
+ for number_item in numbers_data:
872
+ num = number_item["number"]
873
+ num_count = number_item["count"]
874
+ key = f"{rng}-{num}"
875
+ prev_num_count = sms_counter.get(key, 0)
876
 
877
+ if num_count > prev_num_count:
878
+ print(f" πŸ“± Nomor: {mask_number(num)}")
879
+ print(f" πŸ“¨ {prev_num_count} β†’ {num_count} SMS")
880
+ sms_counter[key] = num_count
881
+
882
+ all_sms = get_sms_fast(rng, num)
883
+ new_sms = all_sms[prev_num_count:]
 
 
 
884
 
885
+ for service, sms, otp in new_sms:
886
+ if otp:
887
+ sms_id = f"{rng}-{num}-{otp}"
888
+ if sms_id not in sent_cache:
889
+ masked = mask_number(num)
890
+
891
+ msg = f"πŸ”” *NEW OTP*\n🌍 {country}\nπŸ“ž `{masked}`\nπŸ’¬ {service}\nπŸ” `{otp}`\n\n{sms[:300]}"
892
+
893
+ if tg_send(msg):
894
+ sent_cache.add(sms_id)
895
+ add_otp_log(country, masked, service, otp, sms)
896
+ print(f" βœ… OTP: {otp} - {service}")
897
+
898
+ print(f" βœ… Selesai proses {country}")
899
+ time.sleep(0.5)
900
 
901
+ print("\n⏳ Tidur 2 detik...")
902
+ time.sleep(2)
903
 
904
  except Exception as e:
905
+ print(f"❌ Error: {e}")
906
+ time.sleep(3)
 
 
907
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
908
  if __name__ == "__main__":
909
+ try:
910
+ main()
911
+ except KeyboardInterrupt:
912
+ print("\nπŸ›‘ BOT STOPPED")
requirement.txt CHANGED
@@ -1,3 +1,4 @@
1
- httpx
2
- beautifulsoup4
3
- gradio
 
 
1
+ flask==2.3.3
2
+ httpx==0.25.2
3
+ beautifulsoup4==4.12.2
4
+ gunicorn==21.2.0