CoolFace
Apppublic

emltechstudio/Quote

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
main.py439 linesDownload Raw Back to root
1from fastapi import FastAPI, Request, HTTPException2from fastapi.middleware.cors import CORSMiddleware3from fastapi.responses import HTMLResponse4from pydantic import BaseModel5import httpx6import os7import time8import hmac9import hashlib10import base6411import secrets12from collections import defaultdict13from datetime import datetime, timedelta14 15app = FastAPI()16 17app.add_middleware(18    CORSMiddleware,19    allow_origins=["*"],20    allow_methods=["*"],21    allow_headers=["*"],22)23 24# ── CONFIG ────────────────────────────────────────────────────25GROQ_API_KEY       = os.environ.get("GROQ_API_KEY")26KEY_SECRET         = os.environ.get("KEY_SECRET", "change_this_in_hf_secrets")27ADMIN_SECRET       = os.environ.get("ADMIN_SECRET", "change_this_in_hf_secrets")28GROQ_URL           = "https://api.groq.com/openai/v1/chat/completions"29SELAR_PRO_LINK     = "https://selar.com/271q02q156"30SELAR_PREMIUM_LINK = "https://selar.com/077jw01q60"31APP_URL            = "https://quotes.afric.site/app.html"32 33ALLOWED_REFERRERS  = ["selar.com", "emltechstudio.site", "quotes.afric.site"]34SNAP_LIMITS        = {"free": 1, "pro": 2, "premium": 5}35 36# ── STORES ────────────────────────────────────────────────────37rate_store    = defaultdict(float)38snap_store    = defaultdict(lambda: {"count": 0, "date": ""})39analytics_log = []40admin_keys    = []  # tracks keys generated via admin panel41 42# ── HELPERS ───────────────────────────────────────────────────43def get_ip(r: Request) -> str:44    fwd = r.headers.get("X-Forwarded-For")45    return fwd.split(",")[0].strip() if fwd else r.client.host46 47def check_rate(ip: str):48    now = time.time()49    if now - rate_store[ip] < 30:50        raise HTTPException(429, f"Wait {int(30-(now-rate_store[ip]))}s.")51    rate_store[ip] = now52 53def check_referrer(r: Request):54    ref = r.headers.get("referer","") + r.headers.get("origin","")55    if not any(d in ref for d in ALLOWED_REFERRERS):56        raise HTTPException(403, "Access denied.")57 58def sign_key(payload: str) -> str:59    return hmac.new(60        KEY_SECRET.encode(), payload.encode(), hashlib.sha25661    ).hexdigest()[:24]62 63NONCES_FILE = "/data/used_nonces.txt"64 65def load_used_nonces() -> set:66    try:67        with open(NONCES_FILE, "r") as f:68            return set(line.strip() for line in f if line.strip())69    except FileNotFoundError:70        return set()71 72def save_nonce(nonce: str):73    try:74        os.makedirs("/data", exist_ok=True)75        with open(NONCES_FILE, "a") as f:76            f.write(nonce + "\n")77    except Exception:78        pass79 80def make_key(tier: str, days: int, single_use: bool = False) -> str:81    expiry  = (datetime.utcnow() + timedelta(days=days)).strftime("%Y-%m-%d")82    nonce   = secrets.token_hex(6)83    su_flag = "1" if single_use else "0"84    payload = f"{tier}|{expiry}|{nonce}|{su_flag}"85    sig     = sign_key(payload)86    raw     = f"{payload}|{sig}"87    return base64.urlsafe_b64encode(raw.encode()).decode().rstrip("=")88 89def parse_key(key: str, consume: bool = False):90    try:91        pad = key + "=" * (-len(key) % 4)92        raw = base64.urlsafe_b64decode(pad.encode()).decode()93        parts = raw.split("|")94        if len(parts) != 5:95            raise ValueError96        tier, expiry, nonce, su_flag, sig = parts97        payload  = f"{tier}|{expiry}|{nonce}|{su_flag}"98        expected = sign_key(payload)99        if not hmac.compare_digest(sig, expected):100            raise HTTPException(400, "Invalid key.")101        if datetime.utcnow() > datetime.strptime(expiry, "%Y-%m-%d"):102            raise HTTPException(400, "Key has expired. Please renew your plan.")103        if tier not in ("pro", "premium"):104            raise ValueError105        if su_flag == "1":106            used = load_used_nonces()107            if nonce in used:108                raise HTTPException(400, "This activation link has already been used.")109            if consume:110                save_nonce(nonce)111        return tier, expiry112    except HTTPException:113        raise114    except Exception:115        raise HTTPException(400, "Invalid activation key.")116 117# ── ACTIVATION PAGE ───────────────────────────────────────────118def activation_html(tier: str, key: str) -> str:119    label  = "Pro" if tier == "pro" else "Premium"120    color  = "#2563eb" if tier == "pro" else "#ea580c"121    r,g,b  = int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)122    deep   = f"{APP_URL}?key={key}"123    return f"""<!DOCTYPE html>124<html lang="en">125<head>126<meta charset="UTF-8">127<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">128<meta name="theme-color" content="#0a0a0a">129<title>Activate Quotes {label}</title>130<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">131<style>132*{{margin:0;padding:0;box-sizing:border-box}}133:root{{--bg:#0a0a0a;--s1:#111;--s2:#1a1a1a;--bd:#222;--tx:#f2f2ef;--t2:#888;--t3:#444;134  --acc:{color};--fn:'Syne',sans-serif;--mn:'JetBrains Mono',monospace}}135body{{background:var(--bg);color:var(--tx);font-family:var(--fn);min-height:100svh;136  display:flex;flex-direction:column;align-items:center;justify-content:center;137  padding:24px;-webkit-font-smoothing:antialiased}}138.card{{background:var(--s1);border:1px solid var(--bd);border-radius:20px;139  padding:32px 28px;max-width:420px;width:100%;display:flex;flex-direction:column;gap:20px}}140.logo{{display:flex;align-items:center;gap:10px}}141.logo-icon{{width:36px;height:36px;border-radius:9px;background:var(--s2);border:1px solid var(--bd);142  display:flex;align-items:center;justify-content:center}}143.logo-name{{font-size:.95rem;font-weight:800}}144.logo-name span{{color:var(--acc)}}145.badge{{display:inline-flex;align-items:center;gap:6px;146  background:rgba({r},{g},{b},0.12);color:var(--acc);147  border:1px solid rgba({r},{g},{b},0.25);148  border-radius:20px;padding:5px 12px;font-family:var(--mn);font-size:.68rem;font-weight:600;width:fit-content}}149h1{{font-size:1.5rem;font-weight:800;letter-spacing:-.03em;line-height:1.2}}150.warn-box{{background:rgba(234,88,12,0.08);border:1px solid rgba(234,88,12,0.2);151  border-radius:10px;padding:12px 14px;display:flex;align-items:flex-start;gap:10px}}152.warn-box svg{{flex-shrink:0;margin-top:1px}}153.warn-text{{font-family:var(--mn);font-size:.68rem;color:#fb923c;line-height:1.7}}154.warn-text strong{{color:#fed7aa}}155.timer-box{{text-align:center}}156.timer-label{{font-family:var(--mn);font-size:.6rem;color:var(--t3);157  text-transform:uppercase;letter-spacing:.8px;margin-bottom:4px}}158.timer{{font-family:var(--mn);font-size:1.8rem;font-weight:700;color:var(--acc)}}159.timer.urgent{{color:#e03c3c}}160.btn-activate{{width:100%;padding:16px;background:var(--acc);border:none;border-radius:12px;161  color:#fff;font-family:var(--fn);font-size:1.1rem;font-weight:800;cursor:pointer;162  transition:all .15s;display:flex;align-items:center;justify-content:center;gap:10px}}163.btn-activate:hover{{opacity:.88;transform:translateY(-1px)}}164.btn-activate:active{{transform:scale(.97)}}165.btn-activate:disabled{{opacity:.4;cursor:not-allowed;transform:none}}166.steps{{display:flex;flex-direction:column;gap:10px}}167.step{{display:flex;align-items:flex-start;gap:10px}}168.sn{{width:20px;height:20px;border-radius:50%;background:rgba({r},{g},{b},0.12);169  color:var(--acc);font-family:var(--mn);font-size:.6rem;font-weight:700;170  display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:2px}}171.st{{font-family:var(--mn);font-size:.7rem;color:var(--t2);line-height:1.6}}172.st strong{{color:var(--tx)}}173.expired-box{{display:none;background:#1a1a1a;border:1px solid #333;border-radius:12px;174  padding:20px;text-align:center}}175.expired-box h3{{font-size:1rem;font-weight:800;color:#e03c3c;margin-bottom:8px}}176.expired-box p{{font-family:var(--mn);font-size:.72rem;color:var(--t2);line-height:1.7}}177footer{{margin-top:16px;font-family:var(--mn);font-size:.6rem;color:var(--t3);text-align:center}}178</style>179</head>180<body>181<div class="card">182  <div class="logo">183    <div class="logo-icon">184      <svg width="20" height="20" viewBox="0 0 24 24" fill="none">185        <circle cx="10" cy="10" r="6" stroke="{color}" stroke-width="2"/>186        <path d="M13 7L10 11H12L9 15" stroke="#2563eb" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>187        <line x1="15" y1="15" x2="19" y2="19" stroke="{color}" stroke-width="2" stroke-linecap="round"/>188      </svg>189    </div>190    <div class="logo-name">Quo<span>tes</span></div>191  </div>192 193  <div class="badge">194    <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>195    {label} Plan — Payment Confirmed196  </div>197 198  <h1>One tap to activate.</h1>199 200  <div class="warn-box">201    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fb923c" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>202    <div class="warn-text"><strong>Do not close this page</strong> before tapping Activate Now. This link works once only — if you leave without activating, you will need to contact support.</div>203  </div>204 205  <div class="timer-box" id="timer-box">206    <div class="timer-label">This link expires in</div>207    <div class="timer" id="timer">05:00</div>208  </div>209 210  <div id="activate-section">211    <button class="btn-activate" id="activate-btn" onclick="doActivate()">212      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg>213      Activate Now — Open App214    </button>215  </div>216 217  <div class="expired-box" id="expired-box">218    <h3>This link has expired</h3>219    <p>You did not activate within the time limit.<br>Please contact us to get a new activation link.<br><br>220    <a href="mailto:emltechstudio@gmail.com" style="color:{color}">emltechstudio@gmail.com</a></p>221  </div>222 223  <div class="steps">224    <div class="step"><div class="sn">1</div><div class="st">Tap <strong>Activate Now</strong> — opens the Quotes app automatically</div></div>225    <div class="step"><div class="sn">2</div><div class="st">Your <strong>{label} plan</strong> activates instantly — no further steps needed</div></div>226    <div class="step"><div class="sn">3</div><div class="st">If the app does not open, make sure Quotes is installed on your device first</div></div>227  </div>228</div>229<footer>Quotes by EML Tech Studio &middot; emltechstudio@gmail.com</footer>230<script>231let activated = false;232const TIMEOUT = 5 * 60; // 5 minutes233let remaining = TIMEOUT;234 235function pad(n){{ return String(n).padStart(2,'0') }}236 237const timerEl = document.getElementById('timer');238const interval = setInterval(()=>{{239  remaining--;240  const m = Math.floor(remaining/60);241  const s = remaining%60;242  timerEl.textContent = pad(m)+':'+pad(s);243  if(remaining <= 60) timerEl.classList.add('urgent');244  if(remaining <= 0){{245    clearInterval(interval);246    document.getElementById('activate-btn').disabled = true;247    document.getElementById('activate-section').style.display = 'none';248    document.getElementById('expired-box').style.display = 'block';249    document.getElementById('timer-box').style.display = 'none';250  }}251}}, 1000);252 253function doActivate(){{254  activated = true;255  clearInterval(interval);256  document.getElementById('timer-box').style.display = 'none';257  document.getElementById('activate-btn').disabled = true;258  document.getElementById('activate-btn').innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg> Activating...';259  window.location.href = '{deep}';260}}261 262window.addEventListener('beforeunload', e=>{{263  if(!activated){{264    e.preventDefault();265    e.returnValue = 'Do not leave this page without tapping Activate Now — your access will be lost.';266    return e.returnValue;267  }}268}});269</script>270</body>271</html>"""272 273 274# ── ADMIN PAGE ────────────────────────────────────────────────275ADMIN_HTML = """<!DOCTYPE html>276<html lang="en">277<head>278<meta charset="UTF-8">279<meta name="viewport" content="width=device-width,initial-scale=1">280<title>Quotes Admin</title>281<link href="https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">282<style>283*{margin:0;padding:0;box-sizing:border-box}284body{background:#0a0a0a;color:#f2f2ef;font-family:'Syne',sans-serif;min-height:100svh;display:flex;align-items:center;justify-content:center;padding:24px}285.card{background:#111;border:1px solid #222;border-radius:20px;padding:32px;max-width:480px;width:100%;display:flex;flex-direction:column;gap:18px}286h1{font-size:1.4rem;font-weight:800;letter-spacing:-.03em}287.fi{display:flex;flex-direction:column;gap:6px}288label{font-family:'JetBrains Mono',monospace;font-size:.62rem;color:#555;text-transform:uppercase;letter-spacing:.8px}289input,select{background:#1a1a1a;border:1.5px solid #222;border-radius:8px;color:#f2f2ef;font-family:'JetBrains Mono',monospace;font-size:.9rem;padding:11px 13px;outline:none;transition:border-color .15s;-webkit-appearance:none}290input:focus,select:focus{border-color:#ea580c}291select option{background:#1a1a1a}292.btn{width:100%;padding:13px;background:#ea580c;border:none;border-radius:10px;color:#fff;font-family:'Syne',sans-serif;font-size:1rem;font-weight:800;cursor:pointer;transition:opacity .15s}293.btn:hover{opacity:.88}294.result{background:#1a1a1a;border:1.5px solid #222;border-radius:10px;padding:14px;font-family:'JetBrains Mono',monospace;font-size:.75rem;color:#ea580c;word-break:break-all;line-height:1.7;display:none}295.stats{background:#1a1a1a;border:1.5px solid #222;border-radius:10px;padding:14px;font-family:'JetBrains Mono',monospace;font-size:.72rem;color:#888;line-height:1.8;display:none}296.row{display:grid;grid-template-columns:1fr 1fr;gap:12px}297</style>298</head>299<body>300<div class="card">301  <h1>Quotes Admin</h1>302  <div class="fi"><label>Admin Secret</label><input type="password" id="secret" placeholder="Enter admin secret"></div>303  <div class="row">304    <div class="fi"><label>Tier</label>305      <select id="tier"><option value="pro">Pro</option><option value="premium">Premium</option></select>306    </div>307    <div class="fi"><label>Duration (days)</label><input type="number" id="days" value="31" min="1" max="365"></div>308  </div>309  <button class="btn" onclick="genKey()">Generate Activation Key</button>310  <div class="result" id="result"></div>311  <button class="btn" style="background:#222;color:#888;margin-top:4px" onclick="getStats()">View Analytics</button>312  <div class="stats" id="stats"></div>313</div>314<script>315const BASE = '';316async function genKey(){317  const secret=document.getElementById('secret').value;318  const tier=document.getElementById('tier').value;319  const days=document.getElementById('days').value;320  const r=await fetch(BASE+'/admin/generate-key',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({secret,tier,days:parseInt(days)})});321  const d=await r.json();322  const el=document.getElementById('result');323  el.style.display='block';324  el.textContent=d.key||d.detail||'Error';325}326async function getStats(){327  const secret=document.getElementById('secret').value;328  const r=await fetch(BASE+'/analytics?secret='+secret);329  const d=await r.json();330  const el=document.getElementById('stats');331  el.style.display='block';332  el.textContent=JSON.stringify(d,null,2);333}334</script>335</body>336</html>"""337 338# ── ROUTES ────────────────────────────────────────────────────339 340@app.get("/activate/pro", response_class=HTMLResponse)341async def activate_pro(request: Request):342    check_referrer(request)343    key = make_key("pro", 31, single_use=True)344    return activation_html("pro", key)345 346@app.get("/activate/premium", response_class=HTMLResponse)347async def activate_premium(request: Request):348    check_referrer(request)349    key = make_key("premium", 31, single_use=True)350    return activation_html("premium", key)351 352@app.post("/verify-key")353async def verify_key_endpoint(body: dict):354    key = body.get("key","")355    consume = body.get("consume", False)356    if not key:357        raise HTTPException(400, "No key provided.")358    tier, expiry = parse_key(key, consume=consume)359    return {"valid": True, "tier": tier, "expiry": expiry,360            "label": "Pro" if tier == "pro" else "Premium"}361 362@app.get("/pricing")363async def pricing():364    return {"pro": SELAR_PRO_LINK, "premium": SELAR_PREMIUM_LINK}365 366@app.get("/admin", response_class=HTMLResponse)367async def admin_page():368    return ADMIN_HTML369 370@app.post("/admin/generate-key")371async def admin_generate(body: dict):372    if body.get("secret") != ADMIN_SECRET:373        raise HTTPException(403, "Wrong admin secret.")374    tier = body.get("tier","pro")375    days = int(body.get("days", 31))376    if tier not in ("pro","premium"):377        raise HTTPException(400, "tier must be pro or premium.")378    if not (1 <= days <= 3650):379        raise HTTPException(400, "days must be 1–3650.")380    key = make_key(tier, days)381    admin_keys.append({"tier":tier,"days":days,"ts":datetime.utcnow().isoformat()})382    return {"key": key, "tier": tier, "expires_in_days": days}383 384class SnapRequest(BaseModel):385    system_prompt: str386    user_message:  str387    image_base64:  str = ""388    mime_type:     str = "image/jpeg"389    doc_type:      str = "general"390    tier:          str = "free"391 392@app.post("/generate")393async def generate(request: Request, body: SnapRequest):394    ip = get_ip(request)395    check_rate(ip)396    today = datetime.utcnow().strftime("%Y-%m-%d")397    store = snap_store[ip]398    if store["date"] != today:399        store["count"] = 0400        store["date"]  = today401    limit = SNAP_LIMITS.get(body.tier, 1)402    if store["count"] >= limit:403        raise HTTPException(429, f"Daily AI Snap limit reached ({limit}/day on {body.tier} plan).")404    store["count"] += 1405    analytics_log.append({"ip":ip,"ts":datetime.utcnow().isoformat(),"type":body.doc_type,"tier":body.tier})406 407    user_content = []408    if body.image_base64:409        user_content.append({"type":"image_url","image_url":{"url":f"data:{body.mime_type};base64,{body.image_base64}"}})410    user_content.append({"type":"text","text":body.user_message})411 412    async with httpx.AsyncClient(timeout=30) as client:413        resp = await client.post(GROQ_URL,414            json={"model":"meta-llama/llama-4-scout-17b-16e-instruct",415                  "messages":[{"role":"system","content":body.system_prompt},416                               {"role":"user","content":user_content}],417                  "max_tokens":1024,"temperature":0.2},418            headers={"Authorization":f"Bearer {GROQ_API_KEY}","Content-Type":"application/json"})419    if resp.status_code != 200:420        raise HTTPException(resp.status_code, resp.text)421    return {"result": resp.json()["choices"][0]["message"]["content"]}422 423@app.get("/analytics")424async def analytics(secret: str = ""):425    if secret != ADMIN_SECRET:426        raise HTTPException(403, "Forbidden")427    return {"total":len(analytics_log),428            "unique_ips":len(set(e["ip"] for e in analytics_log)),429            "admin_keys_issued":len(admin_keys),430            "recent":analytics_log[-50:]}431 432@app.get("/")433async def health():434    return {"status":"live","app":"Quotes API","version":"2.0"}435 436# ================= RUN =================437if __name__ == "__main__":438    port = int(os.getenv("PORT", 7860))439    uvicorn.run(app, host="0.0.0.0", port=port)