Skydata001 commited on
Commit
5340f1e
ยท
verified ยท
1 Parent(s): 7500b56

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +713 -0
  2. requirements.txt +3 -0
  3. server-verify-example.js +172 -0
app.py ADDED
@@ -0,0 +1,713 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
3
+ โ•‘ SkyGuard CAPTCHA โ€” Hugging Face Space โ•‘
4
+ โ•‘ ู†ุธุงู… ุชุญู‚ู‚ ุฐูƒูŠ ู„ู…ู†ุน ุงู„ุจูˆุชุงุช ูˆู‡ุฌู…ุงุช DDoS โ•‘
5
+ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
6
+
7
+ ุงู„ู…ูŠุฒุงุช:
8
+ โœ… ุชุญุฏูŠุงุช CAPTCHA (ุฑูŠุงุถูŠุงุช + ุฃุณุฆู„ุฉ ุนุฑุจูŠุฉ + ุตูˆุฑ ASCII)
9
+ โœ… ุญุธุฑ IP ู„ู…ุฏุฉ 5 ุฏู‚ุงุฆู‚ ุนู†ุฏ ุงู„ูุดู„ ุงู„ู…ุชูƒุฑุฑ
10
+ โœ… ูƒุดู ุงู„ุจูˆุชุงุช ุนุจุฑ User-Agent
11
+ โœ… ูƒุดู DDoS (ุทู„ุจุงุช ุฒุงุฆุฏุฉ ููŠ ุฏู‚ูŠู‚ุฉ ูˆุงุญุฏุฉ)
12
+ โœ… ุชูˆู„ูŠุฏ Token ู…ูˆู‚ู‘ุน ู„ู„ุชุญู‚ู‚ ู…ู† ุงู„ู†ุฌุงุญ
13
+ โœ… API ู„ู„ุชุญู‚ู‚ ู…ู† ุตุญุฉ Token
14
+ โœ… ุฏุนู… ุชุถู…ูŠู† iframe ููŠ ุงู„ู…ูˆุงู‚ุน ุงู„ุฃุฎุฑู‰
15
+ """
16
+
17
+ import gradio as gr
18
+ import random
19
+ import time
20
+ import hashlib
21
+ import hmac
22
+ import os
23
+ import json
24
+ import re
25
+ from collections import defaultdict
26
+ from fastapi.responses import JSONResponse
27
+ from fastapi import Request as FastAPIRequest
28
+
29
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
30
+ # ุงู„ุฅุนุฏุงุฏุงุช
31
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
32
+ SECRET_KEY = os.environ.get("SECRET_KEY", "skyguard-secret-key-change-me-2024")
33
+ BLOCK_DURATION = 300 # ุซูˆุงู†ูŠ = 5 ุฏู‚ุงุฆู‚
34
+ MAX_FAILURES = 3 # ู…ุญุงูˆู„ุงุช ุฎุงุทุฆุฉ ู‚ุจู„ ุงู„ุญุธุฑ
35
+ DDOS_THRESHOLD = 25 # ุทู„ุจ/ุฏู‚ูŠู‚ุฉ ู‚ุจู„ ุงุนุชุจุงุฑู‡ุง DDoS
36
+ TOKEN_TTL = 600 # ุตู„ุงุญูŠุฉ ุงู„ุชูˆูƒู† 10 ุฏู‚ุงุฆู‚
37
+
38
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
39
+ # ุงู„ุชุฎุฒูŠู† ููŠ ุงู„ุฐุงูƒุฑุฉ
40
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
41
+ ip_store = defaultdict(lambda: {
42
+ "failures": 0,
43
+ "blocked_until": 0.0,
44
+ "req_times": [], # ู„ูƒุดู DDoS
45
+ "total_reqs": 0,
46
+ "flagged_bot": False,
47
+ })
48
+ token_store: dict[str, float] = {} # token โ†’ expiry_unix
49
+
50
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
51
+ # ุฃู†ู…ุงุท User-Agent ู„ู„ุจูˆุชุงุช
52
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
53
+ BOT_PATTERNS = [
54
+ r"bot\b", r"crawler", r"spider", r"scraper",
55
+ r"\bwget\b", r"\bcurl\b", r"python-?requests",
56
+ r"java\/", r"go-http-client", r"scrapy",
57
+ r"phantomjs", r"headlesschrome", r"selenium",
58
+ r"puppeteer", r"playwright", r"mechanize",
59
+ r"libwww", r"httpclient", r"okhttp",
60
+ r"axios\/", r"node-fetch", r"got\/",
61
+ ]
62
+ BOT_REGEX = re.compile("|".join(BOT_PATTERNS), re.IGNORECASE)
63
+
64
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
65
+ # ุงู„ุฏูˆุงู„ ุงู„ู…ุณุงุนุฏุฉ
66
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
67
+
68
+ def get_ip(request: gr.Request) -> str:
69
+ """ุงุณุชุฎุฑุงุฌ IP ุงู„ุญู‚ูŠู‚ูŠ (ูŠุฏุนู… CDN ู…ุซู„ Cloudflare)"""
70
+ if not request:
71
+ return "0.0.0.0"
72
+ for h in ["cf-connecting-ip", "x-forwarded-for", "x-real-ip", "true-client-ip"]:
73
+ val = request.headers.get(h, "")
74
+ if val:
75
+ return val.split(",")[0].strip()
76
+ if hasattr(request, "client") and request.client:
77
+ return request.client.host
78
+ return "0.0.0.0"
79
+
80
+ def get_ua(request: gr.Request) -> str:
81
+ if not request:
82
+ return ""
83
+ return request.headers.get("user-agent", "")
84
+
85
+ def is_bot_ua(ua: str) -> bool:
86
+ """ูƒุดู ุจูˆุช ุนุจุฑ User-Agent"""
87
+ if not ua or len(ua) < 10:
88
+ return True # ุจุฏูˆู† UA = ู…ุดุจูˆู‡
89
+ return bool(BOT_REGEX.search(ua))
90
+
91
+ def track_and_check_ddos(ip: str) -> bool:
92
+ """
93
+ ูŠุถูŠู ุงู„ุทู„ุจ ุงู„ุญุงู„ูŠ ูˆูŠุฑุฌุน True ุฅุฐุง ุชุฌุงูˆุฒ ุงู„ุญุฏ (DDoS)
94
+ """
95
+ now = time.time()
96
+ store = ip_store[ip]
97
+ store["req_times"] = [t for t in store["req_times"] if now - t < 60]
98
+ store["req_times"].append(now)
99
+ store["total_reqs"] += 1
100
+
101
+ if len(store["req_times"]) > DDOS_THRESHOLD:
102
+ store["blocked_until"] = now + BLOCK_DURATION
103
+ return True
104
+ return False
105
+
106
+ def blocked_status(ip: str) -> tuple[bool, int]:
107
+ """(is_blocked, seconds_remaining)"""
108
+ remaining = ip_store[ip]["blocked_until"] - time.time()
109
+ return remaining > 0, max(0, int(remaining))
110
+
111
+ def fmt_remaining(secs: int) -> str:
112
+ m, s = divmod(secs, 60)
113
+ return f"{m}:{s:02d}"
114
+
115
+ def sign_token(ip: str) -> str:
116
+ """ุชูˆู„ูŠุฏ ุชูˆูƒู† HMAC ู…ูˆู‚ู‘ุน"""
117
+ ts = str(int(time.time()))
118
+ msg = f"{ip}|{ts}"
119
+ sig = hmac.new(SECRET_KEY.encode(), msg.encode(), hashlib.sha256).hexdigest()[:24]
120
+ token = f"{sig}.{ts}"
121
+ token_store[token] = time.time() + TOKEN_TTL
122
+ # ุชู†ุธูŠู ุงู„ุชูˆูƒู†ุงุช ุงู„ู…ู†ุชู‡ูŠุฉ
123
+ expired = [k for k, v in list(token_store.items()) if v < time.time()]
124
+ for k in expired:
125
+ del token_store[k]
126
+ return token
127
+
128
+ def verify_token_logic(token: str) -> dict:
129
+ """ุงู„ุชุญู‚ู‚ ู…ู† ุตุญุฉ ุงู„ุชูˆูƒู†"""
130
+ now = time.time()
131
+ if token in token_store and token_store[token] > now:
132
+ return {"valid": True, "expires_in": int(token_store[token] - now)}
133
+ return {"valid": False, "reason": "token_invalid_or_expired"}
134
+
135
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
136
+ # ุชูˆู„ูŠุฏ ุงู„ุชุญุฏูŠุงุช
137
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
138
+
139
+ def make_math() -> tuple[str, str]:
140
+ op = random.choice(["+", "-", "ร—"])
141
+ if op == "+":
142
+ a, b = random.randint(8, 30), random.randint(8, 30)
143
+ return f"{a} + {b} = ุŸ", str(a + b)
144
+ elif op == "-":
145
+ a = random.randint(20, 50)
146
+ b = random.randint(1, a - 1)
147
+ return f"{a} - {b} = ุŸ", str(a - b)
148
+ else:
149
+ a, b = random.randint(2, 12), random.randint(2, 12)
150
+ return f"{a} ร— {b} = ุŸ", str(a * b)
151
+
152
+ WORD_QA = [
153
+ ("ูƒู… ุนุฏุฏ ุฃูŠุงู… ุงู„ุฃุณุจูˆุนุŸ", "7"),
154
+ ("ูƒู… ุนุฏุฏ ุดู‡ูˆุฑ ุงู„ุณู†ุฉุŸ", "12"),
155
+ ("ูƒู… ุนุฏุฏ ุฃุตุงุจุน ุงู„ูŠุฏูŠู† ู…ุนุงู‹ุŸ", "10"),
156
+ ("ูƒู… ุณุงุนุฉ ููŠ ุงู„ูŠูˆู… ุงู„ูˆุงุญุฏุŸ", "24"),
157
+ ("ูƒู… ุฏู‚ูŠู‚ุฉ ููŠ ุงู„ุณุงุนุฉ ุงู„ูˆุงุญุฏุฉุŸ", "60"),
158
+ ("ูƒู… ุซุงู†ูŠุฉ ููŠ ุงู„ุฏู‚ูŠู‚ุฉ ุงู„ูˆุงุญุฏุฉุŸ", "60"),
159
+ ("ู…ุง ุนุงุตู…ุฉ ุงู„ู…ู…ู„ูƒุฉ ุงู„ุนุฑุจูŠุฉ ุงู„ุณุนูˆุฏูŠุฉุŸ", "ุงู„ุฑูŠุงุถ"),
160
+ ("ู…ุง ุนุงุตู…ุฉ ุงู„ุฅู…ุงุฑุงุช ุงู„ุนุฑุจูŠุฉ ุงู„ู…ุชุญุฏุฉุŸ", "ุฃุจูˆุธุจูŠ"),
161
+ ("ู…ุง ู„ูˆู† ุงู„ู†ุจุงุช ุนุงุฏุฉู‹ุŸ", "ุฃุฎุถุฑ"),
162
+ ("ู…ุง ู„ูˆู† ุงู„ุณู…ุงุก ุงู„ุตุงููŠุฉ ู†ู‡ุงุฑุงู‹ุŸ", "ุฃุฒุฑู‚"),
163
+ ("ูƒู… ุนุฏุฏ ุฃูŠุงู… ุดู‡ุฑ ูŠู†ุงูŠุฑุŸ", "31"),
164
+ ("ูƒู… ุนุฏุฏ ุฃูŠุงู… ุดู‡ุฑ ูุจุฑุงูŠุฑ ููŠ ุงู„ุณู†ุฉ ุงู„ุนุงุฏูŠุฉุŸ", "28"),
165
+ ]
166
+
167
+ SEQUENCE_QA = [
168
+ ("ุฃูƒู…ู„: 2ุŒ 4ุŒ 6ุŒ 8ุŒ ุŸ", "10"),
169
+ ("ุฃูƒู…ู„: 5ุŒ 10ุŒ 15ุŒ 20ุŒ ุŸ", "25"),
170
+ ("ุฃูƒู…ู„: 1ุŒ 3ุŒ 5ุŒ 7ุŒ ุŸ", "9"),
171
+ ("ุฃูƒู…ู„: 10ุŒ 20ุŒ 30ุŒ ุŸ", "40"),
172
+ ("ุฃูƒู…ู„: 3ุŒ 6ุŒ 9ุŒ 12ุŒ ุŸ", "15"),
173
+ ("ุฃูƒู…ู„: 100ุŒ 90ุŒ 80ุŒ 70ุŒ ุŸ", "60"),
174
+ ]
175
+
176
+ def make_challenge() -> tuple[str, str, str]:
177
+ """โ†’ (icon, question, answer)"""
178
+ r = random.random()
179
+ if r < 0.45:
180
+ q, a = make_math()
181
+ return "๐Ÿงฎ", q, a
182
+ elif r < 0.75:
183
+ q, a = random.choice(WORD_QA)
184
+ return "โ“", q, a
185
+ else:
186
+ q, a = random.choice(SEQUENCE_QA)
187
+ return "๐Ÿ”ข", q, a
188
+
189
+
190
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
191
+ # ูˆุงุฌู‡ุฉ ุงู„ู…ุณุชุฎุฏู… โ€” Gradio
192
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
193
+
194
+ CSS = """
195
+ @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800&display=swap');
196
+
197
+ * { box-sizing: border-box; margin: 0; padding: 0; }
198
+
199
+ body, .gradio-container {
200
+ font-family: 'Tajawal', sans-serif !important;
201
+ background: #0a0e1a !important;
202
+ direction: rtl;
203
+ }
204
+
205
+ .captcha-wrap {
206
+ max-width: 480px;
207
+ margin: 40px auto;
208
+ padding: 0 16px;
209
+ }
210
+
211
+ .captcha-card {
212
+ background: linear-gradient(145deg, #111827, #1a2235);
213
+ border: 1px solid rgba(99,179,237,0.2);
214
+ border-radius: 20px;
215
+ padding: 36px 32px 28px;
216
+ box-shadow: 0 20px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.03);
217
+ position: relative;
218
+ overflow: hidden;
219
+ }
220
+
221
+ .captcha-card::before {
222
+ content: '';
223
+ position: absolute;
224
+ top: -60px; right: -60px;
225
+ width: 200px; height: 200px;
226
+ background: radial-gradient(circle, rgba(99,179,237,0.08) 0%, transparent 70%);
227
+ pointer-events: none;
228
+ }
229
+
230
+ .captcha-header {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 12px;
234
+ margin-bottom: 28px;
235
+ padding-bottom: 20px;
236
+ border-bottom: 1px solid rgba(255,255,255,0.06);
237
+ }
238
+
239
+ .captcha-shield {
240
+ font-size: 32px;
241
+ filter: drop-shadow(0 0 12px rgba(99,179,237,0.5));
242
+ }
243
+
244
+ .captcha-title {
245
+ color: #e2e8f0;
246
+ font-size: 20px;
247
+ font-weight: 800;
248
+ letter-spacing: -0.3px;
249
+ }
250
+
251
+ .captcha-subtitle {
252
+ color: #64748b;
253
+ font-size: 13px;
254
+ font-weight: 400;
255
+ margin-top: 2px;
256
+ }
257
+
258
+ .challenge-box {
259
+ background: rgba(99,179,237,0.05);
260
+ border: 1px solid rgba(99,179,237,0.15);
261
+ border-radius: 14px;
262
+ padding: 24px 20px;
263
+ margin-bottom: 20px;
264
+ text-align: center;
265
+ }
266
+
267
+ .challenge-icon { font-size: 28px; margin-bottom: 10px; display: block; }
268
+
269
+ .challenge-text {
270
+ color: #93c5fd;
271
+ font-size: 22px;
272
+ font-weight: 700;
273
+ letter-spacing: 0.5px;
274
+ }
275
+
276
+ .answer-field input {
277
+ background: rgba(255,255,255,0.05) !important;
278
+ border: 1.5px solid rgba(99,179,237,0.25) !important;
279
+ border-radius: 10px !important;
280
+ color: #e2e8f0 !important;
281
+ font-family: 'Tajawal', sans-serif !important;
282
+ font-size: 16px !important;
283
+ padding: 12px 16px !important;
284
+ text-align: center !important;
285
+ transition: border-color 0.2s, box-shadow 0.2s;
286
+ }
287
+
288
+ .answer-field input:focus {
289
+ border-color: #63b3ed !important;
290
+ box-shadow: 0 0 0 3px rgba(99,179,237,0.15) !important;
291
+ outline: none !important;
292
+ }
293
+
294
+ .answer-field label {
295
+ color: #94a3b8 !important;
296
+ font-family: 'Tajawal', sans-serif !important;
297
+ font-size: 14px !important;
298
+ }
299
+
300
+ .verify-btn button {
301
+ width: 100% !important;
302
+ background: linear-gradient(135deg, #3b82f6, #2563eb) !important;
303
+ border: none !important;
304
+ border-radius: 10px !important;
305
+ color: white !important;
306
+ font-family: 'Tajawal', sans-serif !important;
307
+ font-size: 16px !important;
308
+ font-weight: 700 !important;
309
+ padding: 12px !important;
310
+ cursor: pointer !important;
311
+ transition: transform 0.15s, box-shadow 0.15s !important;
312
+ box-shadow: 0 4px 15px rgba(59,130,246,0.4) !important;
313
+ margin-top: 16px !important;
314
+ }
315
+
316
+ .verify-btn button:hover {
317
+ transform: translateY(-1px) !important;
318
+ box-shadow: 0 6px 20px rgba(59,130,246,0.5) !important;
319
+ }
320
+
321
+ .reload-btn button {
322
+ width: 100% !important;
323
+ background: transparent !important;
324
+ border: 1px solid rgba(255,255,255,0.1) !important;
325
+ border-radius: 10px !important;
326
+ color: #64748b !important;
327
+ font-family: 'Tajawal', sans-serif !important;
328
+ font-size: 14px !important;
329
+ padding: 9px !important;
330
+ cursor: pointer !important;
331
+ margin-top: 10px !important;
332
+ transition: all 0.2s !important;
333
+ }
334
+
335
+ .reload-btn button:hover {
336
+ border-color: rgba(99,179,237,0.3) !important;
337
+ color: #93c5fd !important;
338
+ }
339
+
340
+ .status-area textarea, .status-area input {
341
+ background: transparent !important;
342
+ border: none !important;
343
+ border-radius: 0 !important;
344
+ color: transparent !important;
345
+ height: 0 !important;
346
+ padding: 0 !important;
347
+ display: none !important;
348
+ }
349
+
350
+ .result-area {
351
+ margin-top: 16px;
352
+ min-height: 20px;
353
+ }
354
+
355
+ .result-area textarea {
356
+ background: transparent !important;
357
+ border: none !important;
358
+ color: #64748b !important;
359
+ font-family: 'Tajawal', sans-serif !important;
360
+ font-size: 14px !important;
361
+ text-align: center !important;
362
+ resize: none !important;
363
+ min-height: 40px !important;
364
+ }
365
+
366
+ .footer-info {
367
+ display: flex;
368
+ justify-content: space-between;
369
+ align-items: center;
370
+ margin-top: 20px;
371
+ padding-top: 16px;
372
+ border-top: 1px solid rgba(255,255,255,0.05);
373
+ }
374
+
375
+ .footer-brand {
376
+ color: #334155;
377
+ font-size: 12px;
378
+ }
379
+
380
+ .footer-secure {
381
+ color: #334155;
382
+ font-size: 12px;
383
+ display: flex;
384
+ align-items: center;
385
+ gap: 5px;
386
+ }
387
+
388
+ /* Status colors via JS classes */
389
+ .status-success { color: #4ade80 !important; }
390
+ .status-error { color: #f87171 !important; }
391
+ .status-warn { color: #fbbf24 !important; }
392
+ .status-info { color: #93c5fd !important; }
393
+
394
+ /* Hide Gradio default footer/header */
395
+ footer, .svelte-1rjryqp { display: none !important; }
396
+ #component-0 { padding: 0 !important; }
397
+ """
398
+
399
+ JS_POST_MESSAGE = """
400
+ <script>
401
+ // ุฅุฑุณุงู„ ุงู„ุชูˆูƒู† ุฅู„ู‰ ุงู„ู…ูˆู‚ุน ุงู„ุฃุจ ุนู†ุฏ ู†ุฌุงุญ CAPTCHA
402
+ window._skyguardSuccess = function(token) {
403
+ const msg = { type: 'skyguard-captcha', status: 'success', token: token };
404
+ if (window.parent && window.parent !== window) {
405
+ window.parent.postMessage(msg, '*');
406
+ }
407
+ // ุฅุถุงูุฉ timestamp ู„ู„ุนุฑุถ
408
+ console.log('[SkyGuard] CAPTCHA passed, token:', token);
409
+ };
410
+
411
+ window._skyguardFail = function(reason) {
412
+ const msg = { type: 'skyguard-captcha', status: 'blocked', reason: reason };
413
+ if (window.parent && window.parent !== window) {
414
+ window.parent.postMessage(msg, '*');
415
+ }
416
+ };
417
+
418
+ // ุงู„ุงุณุชู…ุงุน ุฅู„ู‰ ุชุบูŠูŠุฑุงุช ุงู„ู†ุชูŠุฌุฉ
419
+ const observer = new MutationObserver(() => {
420
+ const resultEl = document.querySelector('.result-area textarea');
421
+ if (!resultEl) return;
422
+ const val = resultEl.value || '';
423
+ if (val.startsWith('TOKEN:')) {
424
+ const token = val.replace('TOKEN:', '').trim();
425
+ window._skyguardSuccess(token);
426
+ } else if (val.startsWith('BLOCKED:')) {
427
+ window._skyguardFail(val.replace('BLOCKED:', '').trim());
428
+ }
429
+ });
430
+ document.addEventListener('DOMContentLoaded', () => {
431
+ setTimeout(() => {
432
+ const el = document.querySelector('.result-area');
433
+ if (el) observer.observe(el, { subtree: true, characterData: true, childList: true });
434
+ }, 1500);
435
+ });
436
+ </script>
437
+ """
438
+
439
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
440
+ # ู…ู†ุทู‚ ุงู„ุญุงู„ุฉ
441
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
442
+
443
+ def on_load(request: gr.Request):
444
+ """
445
+ ูŠูุณุชุฏุนู‰ ุนู†ุฏ ุชุญู…ูŠู„ ุงู„ุตูุญุฉ โ€” ูŠูˆู„ู‘ุฏ ุงู„ุชุญุฏูŠ ุฃูˆ ูŠุนุฑุถ ุฑุณุงู„ุฉ ุงู„ุญุธุฑ
446
+ Returns: (challenge_html, answer_input, submit_btn, reload_btn, hidden_answer, hidden_ip, result_text)
447
+ """
448
+ ip = get_ip(request)
449
+ ua = get_ua(request)
450
+
451
+ # ูƒุดู DDoS
452
+ if track_and_check_ddos(ip):
453
+ return _blocked_response(ip, "๐Ÿšจ ู‡ุฌูˆู… DDoS ู…ุญุชู…ู„ โ€” ุชู… ุญุธุฑูƒ ุชู„ู‚ุงุฆูŠุงู‹")
454
+
455
+ # ูƒุดู ุจูˆุช
456
+ if is_bot_ua(ua):
457
+ ip_store[ip]["blocked_until"] = time.time() + BLOCK_DURATION
458
+ ip_store[ip]["flagged_bot"] = True
459
+ return _blocked_response(ip, "๐Ÿค– ุชู… ุงูƒุชุดุงููƒ ูƒุจูˆุช ุชู„ู‚ุงุฆูŠ")
460
+
461
+ # ูุญุต ุงู„ุญุธุฑ
462
+ blocked, remaining = blocked_status(ip)
463
+ if blocked:
464
+ return _blocked_response(ip, f"๐Ÿ”’ ู…ุญุธูˆุฑ โ€” ุงู†ุชุธุฑ {fmt_remaining(remaining)} ุฏู‚ูŠู‚ุฉ")
465
+
466
+ return _challenge_response(ip)
467
+
468
+
469
+ def on_submit(user_ans: str, hidden_ans: str, hidden_ip: str, request: gr.Request):
470
+ """ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุฅุฌุงุจุฉ"""
471
+ ip = get_ip(request)
472
+
473
+ blocked, remaining = blocked_status(ip)
474
+ if blocked:
475
+ msg = f"๐Ÿ”’ ู…ุญุธูˆุฑ ู„ู…ุฏุฉ {fmt_remaining(remaining)} ุฏู‚ูŠู‚ุฉ ุฃุฎุฑู‰"
476
+ return (
477
+ gr.update(),
478
+ gr.update(value=""),
479
+ gr.update(value=f"BLOCKED:{msg}"),
480
+ hidden_ip
481
+ )
482
+
483
+ correct = hidden_ans.strip().lower()
484
+ given = (user_ans or "").strip().lower()
485
+
486
+ if not given:
487
+ return gr.update(), gr.update(), gr.update(value="โš ๏ธ ุงู„ุฑุฌุงุก ุฅุฏุฎุงู„ ุงู„ุฅุฌุงุจุฉ"), hidden_ip
488
+
489
+ if given == correct:
490
+ # ู†ุฌุงุญ
491
+ ip_store[ip]["failures"] = 0
492
+ token = sign_token(ip)
493
+ html = _success_html(token)
494
+ return (
495
+ gr.update(value=html),
496
+ gr.update(value="", visible=False),
497
+ gr.update(value=f"TOKEN:{token}"),
498
+ hidden_ip
499
+ )
500
+ else:
501
+ # ูุดู„
502
+ ip_store[ip]["failures"] += 1
503
+ fails = ip_store[ip]["failures"]
504
+ if fails >= MAX_FAILURES:
505
+ ip_store[ip]["blocked_until"] = time.time() + BLOCK_DURATION
506
+ ip_store[ip]["failures"] = 0
507
+ msg = f"๐Ÿ”’ ุชู… ุญุธุฑูƒ ู„ู…ุฏุฉ 5 ุฏู‚ุงุฆู‚ ุจุนุฏ {MAX_FAILURES} ู…ุญุงูˆู„ุงุช ูุงุดู„ุฉ"
508
+ return (
509
+ gr.update(value=_blocked_html(msg)),
510
+ gr.update(value=""),
511
+ gr.update(value=f"BLOCKED:{msg}"),
512
+ hidden_ip
513
+ )
514
+ left = MAX_FAILURES - fails
515
+ return (
516
+ gr.update(),
517
+ gr.update(value=""),
518
+ gr.update(value=f"โŒ ุฅุฌุงุจุฉ ุฎุงุทุฆุฉ โ€” ุชุจู‚ู‰ {left} ู…ุญุงูˆู„ุฉ/ู…ุญุงูˆู„ุงุช"),
519
+ hidden_ip
520
+ )
521
+
522
+
523
+ def on_reload(request: gr.Request):
524
+ ip = get_ip(request)
525
+ blocked, remaining = blocked_status(ip)
526
+ if blocked:
527
+ return _blocked_response(ip, f"๐Ÿ”’ ู„ุง ูŠุฒุงู„ ู…ุญุธูˆุฑุงู‹ โ€” {fmt_remaining(remaining)} ุฏู‚ูŠู‚ุฉ ู…ุชุจู‚ูŠุฉ")
528
+ return _challenge_response(ip)
529
+
530
+
531
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
532
+ # ู…ุณุงุนุฏูˆ HTML
533
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
534
+
535
+ def _challenge_html(icon: str, question: str) -> str:
536
+ return f"""
537
+ <div class="challenge-box">
538
+ <span class="challenge-icon">{icon}</span>
539
+ <div class="challenge-text">{question}</div>
540
+ </div>
541
+ """
542
+
543
+ def _blocked_html(msg: str) -> str:
544
+ return f"""
545
+ <div style="background:rgba(239,68,68,0.08);border:1px solid rgba(239,68,68,0.3);
546
+ border-radius:14px;padding:24px;text-align:center;">
547
+ <div style="font-size:36px;margin-bottom:12px">๐Ÿ›ก๏ธ</div>
548
+ <div style="color:#f87171;font-size:17px;font-weight:700">{msg}</div>
549
+ <div style="color:#64748b;font-size:13px;margin-top:8px">
550
+ ูŠุชู… ุชุณุฌูŠู„ ู‡ุฐู‡ ุงู„ู…ุญุงูˆู„ุฉ
551
+ </div>
552
+ </div>
553
+ """
554
+
555
+ def _success_html(token: str) -> str:
556
+ short = token[:12] + "โ€ฆ"
557
+ return f"""
558
+ <div style="background:rgba(74,222,128,0.08);border:1px solid rgba(74,222,128,0.3);
559
+ border-radius:14px;padding:24px;text-align:center;">
560
+ <div style="font-size:40px;margin-bottom:10px">โœ…</div>
561
+ <div style="color:#4ade80;font-size:18px;font-weight:800">ุชู… ุงู„ุชุญู‚ู‚ ุจู†ุฌุงุญ!</div>
562
+ <div style="color:#64748b;font-size:12px;margin-top:8px;
563
+ font-family:monospace;word-break:break-all;">
564
+ Token: {short}
565
+ </div>
566
+ <div style="color:#334155;font-size:11px;margin-top:6px">
567
+ ุฌุงุฑูŠ ุงู„ุฅุฑุณุงู„ ู„ู„ู…ูˆู‚ุนโ€ฆ
568
+ </div>
569
+ </div>
570
+ """
571
+
572
+ def _challenge_response(ip: str):
573
+ icon, question, answer = make_challenge()
574
+ ch_html = _challenge_html(icon, question)
575
+ return (
576
+ gr.update(value=ch_html, visible=True),
577
+ gr.update(visible=True, value=""),
578
+ gr.update(visible=True),
579
+ gr.update(visible=True),
580
+ gr.update(value=answer),
581
+ gr.update(value=ip),
582
+ gr.update(value=""),
583
+ )
584
+
585
+ def _blocked_response(ip: str, msg: str):
586
+ return (
587
+ gr.update(value=_blocked_html(msg), visible=True),
588
+ gr.update(visible=False, value=""),
589
+ gr.update(visible=False),
590
+ gr.update(visible=False),
591
+ gr.update(value=""),
592
+ gr.update(value=ip),
593
+ gr.update(value=f"BLOCKED:{msg}"),
594
+ )
595
+
596
+
597
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
598
+ # ุจู†ุงุก ุงู„ูˆุงุฌู‡ุฉ
599
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
600
+
601
+ with gr.Blocks(title="SkyGuard CAPTCHA") as demo:
602
+
603
+ gr.HTML(JS_POST_MESSAGE)
604
+
605
+ with gr.Column(elem_classes="captcha-wrap"):
606
+ gr.HTML("""
607
+ <div class="captcha-card">
608
+ <div class="captcha-header">
609
+ <span class="captcha-shield">๐Ÿ›ก๏ธ</span>
610
+ <div>
611
+ <div class="captcha-title">SkyGuard CAPTCHA</div>
612
+ <div class="captcha-subtitle">ุฃุซุจุช ุฃู†ูƒ ู„ุณุช ุฑูˆุจูˆุชุงู‹</div>
613
+ </div>
614
+ </div>
615
+ """)
616
+
617
+ challenge_html = gr.HTML(value="<div style='height:80px'></div>")
618
+ answer_input = gr.Textbox(
619
+ placeholder="ุงูƒุชุจ ุฅุฌุงุจุชูƒ ู‡ู†ุงโ€ฆ",
620
+ label="ุงู„ุฅุฌุงุจุฉ",
621
+ elem_classes="answer-field",
622
+ visible=True
623
+ )
624
+ submit_btn = gr.Button("โœ… ุชุญู‚ู‚", elem_classes="verify-btn", visible=True)
625
+ reload_btn = gr.Button("๐Ÿ”„ ุณุคุงู„ ุฌุฏูŠุฏ", elem_classes="reload-btn", visible=True)
626
+
627
+ # ุญู‚ูˆู„ ู…ุฎููŠุฉ
628
+ hidden_answer = gr.Textbox(visible=False)
629
+ hidden_ip = gr.Textbox(visible=False)
630
+
631
+ # ุงู„ู†ุชูŠุฌุฉ
632
+ result_text = gr.Textbox(
633
+ label="",
634
+ interactive=False,
635
+ elem_classes="result-area",
636
+ show_label=False
637
+ )
638
+
639
+ gr.HTML("""
640
+ <div class="footer-info">
641
+ <span class="footer-brand">SkyGuard v1.0</span>
642
+ <span class="footer-secure">๐Ÿ”’ ู…ุญู…ูŠ ุจู€ HMAC-SHA256</span>
643
+ </div>
644
+ </div>
645
+ """)
646
+
647
+ # โ”€โ”€ ุงู„ุฃุญุฏุงุซ โ”€โ”€
648
+ OUTPUTS = [challenge_html, answer_input, submit_btn, reload_btn,
649
+ hidden_answer, hidden_ip, result_text]
650
+
651
+ demo.load(on_load, outputs=OUTPUTS)
652
+
653
+ submit_btn.click(
654
+ on_submit,
655
+ inputs=[answer_input, hidden_answer, hidden_ip],
656
+ outputs=[challenge_html, answer_input, result_text, hidden_ip]
657
+ )
658
+
659
+ reload_btn.click(on_reload, outputs=OUTPUTS)
660
+
661
+ answer_input.submit(
662
+ on_submit,
663
+ inputs=[answer_input, hidden_answer, hidden_ip],
664
+ outputs=[challenge_html, answer_input, result_text, hidden_ip]
665
+ )
666
+
667
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
668
+ # API Endpoints (FastAPI ุชุญุช Gradio)
669
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
670
+
671
+ @demo.app.get("/api/verify")
672
+ async def api_verify(token: str, request: FastAPIRequest):
673
+ """
674
+ ู†ู‚ุทุฉ ู†ู‡ุงูŠุฉ ู„ู„ุชุญู‚ู‚ ู…ู† ุงู„ุชูˆูƒู† โ€” ูŠุณุชุฎุฏู…ู‡ุง ู…ูˆู‚ุนูƒ ุงู„ุฎู„ููŠ
675
+ GET /api/verify?token=xxxx
676
+ """
677
+ result = verify_token_logic(token)
678
+ # ุฅุฒุงู„ุฉ ุงู„ุชูˆูƒู† ุจุนุฏ ุงู„ุชุญู‚ู‚ (one-time use)
679
+ if result["valid"] and token in token_store:
680
+ del token_store[token]
681
+ return JSONResponse(result)
682
+
683
+
684
+ @demo.app.get("/api/status")
685
+ async def api_status(request: FastAPIRequest):
686
+ """ุญุงู„ุฉ ุงู„ู†ุธุงู…"""
687
+ ip = request.headers.get("x-forwarded-for", request.client.host if request.client else "?")
688
+ ip = ip.split(",")[0].strip()
689
+ blocked, remaining = blocked_status(ip)
690
+ return JSONResponse({
691
+ "ip": ip,
692
+ "blocked": blocked,
693
+ "remaining_seconds": remaining if blocked else 0,
694
+ "active_tokens": len(token_store),
695
+ "tracked_ips": len(ip_store),
696
+ })
697
+
698
+
699
+ @demo.app.get("/api/health")
700
+ async def api_health():
701
+ return JSONResponse({"status": "ok", "service": "SkyGuard CAPTCHA"})
702
+
703
+
704
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
705
+ # ุงู„ุชุดุบูŠู„
706
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•๏ฟฝ๏ฟฝ๏ฟฝโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
707
+
708
+ # HF Space ูŠุดุบู‘ู„ ุงู„ู…ู„ู ู…ุจุงุดุฑุฉ (ู„ูŠุณ ุนุจุฑ __main__)
709
+ demo.launch(
710
+ server_name="0.0.0.0",
711
+ server_port=7860,
712
+ css=CSS,
713
+ )
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=6.0.0
2
+ fastapi
3
+ uvicorn
server-verify-example.js ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
3
+ * ู…ุซุงู„ ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุชูˆูƒู† ููŠ ุงู„ุฎุงุฏู… ุงู„ุฎู„ููŠ (Node.js)
4
+ * SkyGuard CAPTCHA โ€” Server-Side Token Verification
5
+ * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
6
+ *
7
+ * ุชุซุจูŠุช ุงู„ู…ุชุทู„ุจุงุช:
8
+ * npm install express axios
9
+ *
10
+ * ุชุดุบูŠู„:
11
+ * node server-verify-example.js
12
+ */
13
+
14
+ const express = require("express");
15
+ const axios = require("axios");
16
+ const app = express();
17
+
18
+ app.use(express.json());
19
+
20
+ // โ”€โ”€ ุงู„ุฅุนุฏุงุฏุงุช โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
21
+ const SKYGUARD_SPACE_URL = "https://YOUR_USERNAME-skyguard-captcha.hf.space";
22
+
23
+ // โ”€โ”€ ุงู„ุชุญู‚ู‚ ู…ู† captchaToken ุงู„ู‚ุงุฏู… ู…ู† ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฃู…ุงู…ูŠุฉ โ”€โ”€
24
+ async function verifyCaptchaToken(token) {
25
+ try {
26
+ const url = `${SKYGUARD_SPACE_URL}/api/verify?token=${encodeURIComponent(token)}`;
27
+ const res = await axios.get(url, { timeout: 5000 });
28
+ return res.data.valid === true;
29
+ } catch (err) {
30
+ console.error("[SkyGuard] Verification error:", err.message);
31
+ return false; // ููŠ ุญุงู„ุฉ ุงู„ูุดู„ุŒ ุงุฑูุถ ุงู„ุทู„ุจ
32
+ }
33
+ }
34
+
35
+ // โ”€โ”€ ู…ุซุงู„: ุญู…ุงูŠุฉ ู†ู‚ุทุฉ ู†ู‡ุงูŠุฉ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
36
+ app.post("/api/submit-form", async (req, res) => {
37
+ const { captchaToken, name, email, message } = req.body;
38
+
39
+ // 1) ุชุญู‚ู‚ ู…ู† ูˆุฌูˆุฏ ุงู„ุชูˆูƒู†
40
+ if (!captchaToken) {
41
+ return res.status(400).json({ error: "captcha_required" });
42
+ }
43
+
44
+ // 2) ุชุญู‚ู‚ ู…ู† ุตุญุฉ ุงู„ุชูˆูƒู† ุนุจุฑ SkyGuard API
45
+ const valid = await verifyCaptchaToken(captchaToken);
46
+ if (!valid) {
47
+ return res.status(403).json({ error: "captcha_invalid_or_expired" });
48
+ }
49
+
50
+ // 3) ุงู„ุชูˆูƒู† ุตุญูŠุญ โ€” ู†ูู‘ุฐ ุงู„ู…ู†ุทู‚ ุงู„ุฃุตู„ูŠ
51
+ console.log("โœ… CAPTCHA verified! Processing form:", { name, email });
52
+
53
+ res.json({ success: true, message: "ุชู… ุฅุฑุณุงู„ ุงู„ู†ู…ูˆุฐุฌ ุจู†ุฌุงุญ" });
54
+ });
55
+
56
+ // โ”€โ”€ ู…ุซุงู„: ุญู…ุงูŠุฉ ู†ู‚ุทุฉ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
57
+ app.post("/api/login", async (req, res) => {
58
+ const { username, password, captchaToken } = req.body;
59
+
60
+ if (!captchaToken) {
61
+ return res.status(400).json({ error: "captcha_required" });
62
+ }
63
+
64
+ const valid = await verifyCaptchaToken(captchaToken);
65
+ if (!valid) {
66
+ return res.status(403).json({ error: "captcha_failed" });
67
+ }
68
+
69
+ // โ† ู‡ู†ุง ู…ู†ุทู‚ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุงู„ุฎุงุต ุจูƒ
70
+ console.log("Login attempt by:", username);
71
+
72
+ res.json({ success: true });
73
+ });
74
+
75
+ // โ”€โ”€ ู…ุซุงู„: Middleware ู‚ุงุจู„ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
76
+ async function requireCaptcha(req, res, next) {
77
+ const token =
78
+ req.body?.captchaToken ||
79
+ req.query?.captchaToken ||
80
+ req.headers["x-captcha-token"];
81
+
82
+ if (!token) {
83
+ return res.status(400).json({ error: "captcha_token_missing" });
84
+ }
85
+
86
+ const valid = await verifyCaptchaToken(token);
87
+ if (!valid) {
88
+ return res.status(403).json({ error: "captcha_verification_failed" });
89
+ }
90
+
91
+ next();
92
+ }
93
+
94
+ // ุงุณุชุฎุฏุงู… ุงู„ู€ middleware
95
+ app.post("/api/register", requireCaptcha, (req, res) => {
96
+ res.json({ success: true, message: "ุชู… ุชุณุฌูŠู„ ุงู„ุญุณุงุจ ุจู†ุฌุงุญ" });
97
+ });
98
+
99
+ app.post("/api/contact", requireCaptcha, (req, res) => {
100
+ res.json({ success: true, message: "ุชู… ุฅุฑุณุงู„ ุฑุณุงู„ุชูƒ" });
101
+ });
102
+
103
+ // โ”€โ”€ ุชุดุบูŠู„ ุงู„ุฎุงุฏู… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
104
+ app.listen(3000, () => {
105
+ console.log("โœ… Server running at http://localhost:3000");
106
+ console.log("๐Ÿ”— SkyGuard Space:", SKYGUARD_SPACE_URL);
107
+ });
108
+
109
+
110
+ /**
111
+ * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
112
+ * ู…ุซุงู„ PHP (ุฅุฐุง ูƒู†ุช ุชุณุชุฎุฏู… PHP)
113
+ * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
114
+
115
+ <?php
116
+ function verifySkyGuard(string $token): bool {
117
+ $url = "https://YOUR_USERNAME-skyguard-captcha.hf.space/api/verify?token=" . urlencode($token);
118
+ $ch = curl_init($url);
119
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
120
+ curl_setopt($ch, CURLOPT_TIMEOUT, 5);
121
+ $body = curl_exec($ch);
122
+ curl_close($ch);
123
+ if (!$body) return false;
124
+ $data = json_decode($body, true);
125
+ return ($data['valid'] ?? false) === true;
126
+ }
127
+
128
+ // ุงู„ุงุณุชุฎุฏุงู…:
129
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
130
+ $token = $_POST['captchaToken'] ?? '';
131
+ if (!verifySkyGuard($token)) {
132
+ http_response_code(403);
133
+ die(json_encode(['error' => 'captcha_failed']));
134
+ }
135
+ // โœ… ู…ุชุงุจุนุฉ ุงู„ู…ู†ุทู‚ ุงู„ุฃุตู„ูŠ...
136
+ }
137
+ ?>
138
+ */
139
+
140
+
141
+ /**
142
+ * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
143
+ * ู…ุซุงู„ Python (Flask/Django)
144
+ * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
145
+
146
+ import requests
147
+
148
+ SKYGUARD_URL = "https://YOUR_USERNAME-skyguard-captcha.hf.space"
149
+
150
+ def verify_skyguard(token: str) -> bool:
151
+ try:
152
+ r = requests.get(
153
+ f"{SKYGUARD_URL}/api/verify",
154
+ params={"token": token},
155
+ timeout=5
156
+ )
157
+ return r.json().get("valid") is True
158
+ except Exception:
159
+ return False
160
+
161
+ # Flask ู…ุซุงู„:
162
+ from flask import Flask, request, jsonify
163
+ app = Flask(__name__)
164
+
165
+ @app.route("/submit", methods=["POST"])
166
+ def submit():
167
+ token = request.json.get("captchaToken", "")
168
+ if not verify_skyguard(token):
169
+ return jsonify({"error": "captcha_failed"}), 403
170
+ # โœ… ู…ุชุงุจุนุฉ...
171
+ return jsonify({"success": True})
172
+ */