import os
import asyncio
from datetime import datetime, timedelta, timezone
from contextlib import asynccontextmanager
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import httpx
from ai_engine import engine
from telegram_bot import bot
from modules.agents import MODULES, run_module
from jarvis_links import get_all_links, get_random_template
from database import log_activity, log_chat, log_module_output, start_keepalive
from scheduler import scheduler
SPACE_URL = os.environ.get("SPACE_URL", "https://bakulkrupuk2025-openclaw-neo-bot.hf.space")
SUPABASE_URL_ENV = os.environ.get("SUPABASE_URL", "")
SUPABASE_KEY_ENV = os.environ.get("SUPABASE_KEY", "") or os.environ.get("SUPABASE_ANON_KEY", "")
SUPABASE_HEADERS_P = {
"apikey": SUPABASE_KEY_ENV,
"Authorization": f"Bearer {SUPABASE_KEY_ENV}",
"Content-Type": "application/json",
"Prefer": "return=representation"
}
# Spaces yang harus tetap hidup
SPACES_TO_PING = [
f"{SPACE_URL}/health",
"https://bakulkrupuk2025-jarvis-postiz.hf.space/healthz",
]
# ============================================================
# KEEPALIVE FUNCTIONS
# ============================================================
async def keep_space_alive():
"""Ping diri sendiri tiap 4 menit biar tidak sleep"""
await asyncio.sleep(30)
while True:
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(f"{SPACE_URL}/health")
print(f"💓 Self-ping OK: {r.status_code}")
except Exception as e:
print(f"⚠️ Self-ping failed: {e}")
await asyncio.sleep(240) # 4 menit
async def ping_all_spaces():
"""Ping semua space terkait tiap 5 menit"""
await asyncio.sleep(60)
while True:
for url in SPACES_TO_PING:
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(url)
print(f"📡 Ping {url}: {r.status_code}")
except Exception as e:
print(f"⚠️ Ping failed {url}: {e}")
await asyncio.sleep(5)
await asyncio.sleep(300) # 5 menit
# ============================================================
# LIFESPAN
# ============================================================
@asynccontextmanager
async def lifespan(app: FastAPI):
asyncio.create_task(bot.start_polling())
asyncio.create_task(start_keepalive(SPACE_URL))
asyncio.create_task(scheduler.start())
asyncio.create_task(keep_space_alive())
asyncio.create_task(ping_all_spaces())
print("🚀 Jarvis Empire started!")
yield
bot.stop()
scheduler.stop()
app = FastAPI(title="JARVIS EMPIRE", lifespan=lifespan)
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
connections = []
DASHBOARD_HTML = """
JARVIS EMPIRE
🤖 JARVIS BRAIN
JARVIS
⚡ Sistem online. Selamat datang, Master Haryono. Semua modul aktif. Pinterest queue terhubung. Keepalive berjalan.
⚡ MODULES
📡 OUTPUT
Jalankan modul untuk melihat output
// Output modul muncul di sini...
💰 AFFILIATE LINKS
🏠 HOSTINGER
hostinger.com?REFERRALCODE=FCIHARY08HBD
💵 ~$60-100/sale
🛒 SHOPEE
https://s.shopee.co.id/6pvyNUFq9A
💵 2-10%/sale
📌 PINTEREST QUEUE
Pending—
Ready—
Posted—
📋 ACTIVITY LOG
--:--BOOTSystem initialized
⏰ SCHEDULER
🔥 TrendHijacker6 jam
✍️ Writer Hostinger8 jam
✍️ Writer Shopee10 jam
👻 GhostNetwork12 jam
📌 Pinterest Push6 jam
💓 Keepalive4 menit
"""
# ============================================================
# ROUTES
# ============================================================
@app.get("/", response_class=HTMLResponse)
async def dashboard():
return DASHBOARD_HTML
@app.get("/health")
async def health_check():
return {
"status": "alive",
"service": "JARVIS EMPIRE",
"timestamp": datetime.utcnow().isoformat()
}
@app.get("/api/status")
async def get_status():
stats = engine.get_stats()
from database import SUPABASE_URL
return {
"status": "online",
"available_providers": len(stats["available"]),
"providers": stats["available"],
"active_provider": stats["active_provider"],
"modules": list(MODULES.keys()),
"db_connected": bool(SUPABASE_URL),
"scheduler": "running",
"timestamp": datetime.now().isoformat()
}
@app.post("/api/chat")
async def chat(data: dict):
message = data.get("message", "")
if not message:
return {"response": "Pesan kosong.", "provider": "none"}
result = await engine.think(message)
asyncio.create_task(log_chat("user", message))
asyncio.create_task(log_chat("jarvis", result["response"], result["provider"]))
asyncio.create_task(log_activity("CHAT", message[:50]))
return {"response": result["response"], "provider": result["provider"]}
@app.post("/api/module")
async def run_module_api(data: dict):
module_name = data.get("module", "")
if module_name not in MODULES:
return {"error": "Not found", "output": "Module tidak ditemukan.", "module": module_name, "provider": "none"}
kwargs = {k: v for k, v in data.items() if k != "module"}
try:
result = await MODULES[module_name].run(**kwargs)
asyncio.create_task(log_module_output(
module_name,
result.get("topic", ""),
result.get("output", ""),
result.get("provider", "")
))
asyncio.create_task(log_activity("MODULE", module_name))
return {"module": result.get("module", module_name), "output": result.get("output", ""), "provider": result.get("provider", "unknown")}
except Exception as e:
return {"error": str(e), "output": f"Error: {str(e)}", "module": module_name, "provider": "none"}
@app.get("/api/template/{platform}")
async def get_template(platform: str):
template = get_random_template(platform)
return {"template": template, "platform": platform}
# ============================================================
# PINTEREST ENDPOINTS
# ============================================================
PINTEREST_TOPICS = {
"hostinger": [
"best web hosting for beginners 2025",
"how to start a blog with Hostinger",
"Hostinger review honest comparison",
"cheap web hosting small business",
"WordPress hosting tutorial step by step",
"how to make money blogging affiliate",
],
"general": [
"passive income ideas from home 2025",
"side hustle ideas no investment",
"make money online for beginners",
"work from home productivity tips",
"affiliate marketing beginner guide",
"digital marketing tips small business",
]
}
@app.post("/api/pinterest/generate")
async def pinterest_generate(request: Request):
"""Generate konten Pinterest via Gemini dan push ke Supabase queue"""
import random
try:
body = await request.json()
except:
body = {}
count = min(int(body.get("count", 3)), 5)
niche_filter = body.get("niche", "both")
results = []
for i in range(count):
niche = random.choice(["hostinger", "general"]) if niche_filter == "both" else niche_filter
topic = random.choice(PINTEREST_TOPICS.get(niche, PINTEREST_TOPICS["general"]))
affiliate_link = "https://hostinger.com/?REFERRALCODE=FCIHARY08HBD" if niche == "hostinger" else ""
try:
# Generate via AI engine
prompt = f"""Create Pinterest pin content for: "{topic}"
Niche: {niche}
{f'Include affiliate link: {affiliate_link}' if affiliate_link else ''}
Reply ONLY in JSON (no markdown):
{{"title":"max 100 chars catchy title","description":"200-500 chars engaging description with keywords","image_prompt":"detailed prompt for 1000x1500 Pinterest image","tags":["tag1","tag2","tag3","tag4","tag5"]}}"""
ai_result = await engine.think(prompt)
raw = ai_result.get("response", "")
import json, re
clean = re.sub(r'```json\n?|```\n?', '', raw).strip()
parsed = json.loads(clean)
scheduled_at = (datetime.now(timezone.utc) + timedelta(hours=(i+1)*2)).isoformat()
payload = {
"title": parsed["title"][:100],
"description": parsed["description"][:500],
"affiliate_link": affiliate_link or None,
"niche": niche,
"tags": parsed.get("tags", []),
"image_prompt": parsed.get("image_prompt", f"Pinterest pin about {topic}"),
"status": "pending",
"scheduled_at": scheduled_at
}
async with httpx.AsyncClient(timeout=15) as client:
r = await client.post(
f"{SUPABASE_URL_ENV}/rest/v1/pinterest_content_queue",
headers=SUPABASE_HEADERS_P,
json=payload
)
results.append({"success": True, "topic": topic, "niche": niche, "title": parsed["title"]})
asyncio.create_task(log_activity("PINTEREST", f"Queued: {parsed['title'][:40]}"))
except Exception as e:
results.append({"success": False, "topic": topic, "error": str(e)})
return {
"status": "ok",
"generated": sum(1 for r in results if r["success"]),
"total_requested": count,
"results": results
}
@app.get("/api/pinterest/status")
async def pinterest_status():
"""Status antrian Pinterest"""
try:
async with httpx.AsyncClient(timeout=15) as client:
r = await client.get(
f"{SUPABASE_URL_ENV}/rest/v1/pinterest_content_queue",
headers=SUPABASE_HEADERS_P,
params={"select": "status,niche,title,scheduled_at", "order": "created_at.desc", "limit": "20"}
)
data = r.json()
summary = {}
for item in data:
s = item.get("status", "unknown")
summary[s] = summary.get(s, 0) + 1
return {"status": "ok", "summary": summary, "total": len(data), "recent": data[:5]}
except Exception as e:
return JSONResponse({"error": str(e)}, status_code=500)
@app.post("/api/n8n/webhook")
async def n8n_webhook(request: Request):
"""Terima notifikasi dari n8n setelah pin posted"""
try:
body = await request.json()
asyncio.create_task(log_activity("N8N", f"Pin posted: {body.get('title', '')[:40]}"))
return {"status": "received"}
except Exception as e:
return JSONResponse({"error": str(e)}, status_code=500)
# ============================================================
# WEBSOCKET
# ============================================================
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
connections.append(websocket)
try:
while True:
await websocket.receive_text()
except WebSocketDisconnect:
if websocket in connections:
connections.remove(websocket)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)