merge: chore/cleanup-remove-bloat-and-secrets into main

This commit is contained in:
Crypto Rug Munch 2026-07-02 01:24:22 +07:00
commit bde2f3a97d
1173 changed files with 437609 additions and 0 deletions

305
app/content_platform.py Normal file
View file

@ -0,0 +1,305 @@
"""
Ghost Content Platform @CryptoRugMunch Feed engine.
Post types:
micro Short posts (4000 chars, like X posts)
thread Connected series of posts (X-style threads)
article Standard longform blog post
dev Development updates
announcement Platform announcements
newsletter Weekly deep-dive newsletter
briefing Daily intelligence briefing (auto-generated)
paid Premium gated content
Auth: Session cookie from owner login. Cookie refreshed on 401.
"""
import json
import logging
import os
from datetime import UTC, datetime
from typing import Any
import httpx
logger = logging.getLogger(__name__)
GHOST_API = "https://blog.rugmunch.io"
GHOST_CONTENT_KEY = os.getenv("GHOST_CONTENT_API_KEY", "")
GHOST_SESSION_COOKIE = "/home/dev/rmi/backend/.secrets/ghost_session_cookies"
PROFILE = {
"name": "Rug Munch Intelligence",
"handle": "@CryptoRugMunch",
"avatar": "https://rugmunch.io/logo.png",
"bio": "Crypto security platform. Real-time scam detection, cross-chain forensics, AI-powered investigation. Free. No signup. MCP-native.",
"website": "https://rugmunch.io",
}
POST_TYPES = {
"micro": {"tag": "micro", "label": "Post", "icon": "💬", "limit": 4000},
"thread": {"tag": "thread", "label": "Thread", "icon": "🧵", "limit": 4000},
"article": {"tag": "article", "label": "Longform", "icon": "📄", "limit": 50000},
"dev": {"tag": "dev", "label": "Dev Update", "icon": "", "limit": 4000},
"announcement": {"tag": "announcement", "label": "Announcement", "icon": "📢", "limit": 4000},
"newsletter": {"tag": "newsletter", "label": "Newsletter", "icon": "📰", "limit": 50000},
"briefing": {"tag": "briefing", "label": "Briefing", "icon": "🔍", "limit": 50000},
}
TYPE_LIMITS = {k: v["limit"] for k, v in POST_TYPES.items()}
def _get_session_cookie() -> str | None:
"""Read saved session cookie for Ghost admin auth."""
try:
with open(GHOST_SESSION_COOKIE) as f:
for line in f:
if "ghost-admin-api-session" in line:
return line.strip()
except FileNotFoundError:
pass
return None
async def create_post(
content: str,
post_type: str = "micro",
title: str = "",
tags: list[str] | None = None,
status: str = "published",
visibility: str = "public",
) -> dict[str, Any]:
"""Create any type of post on Ghost."""
tags = (tags or []) + [POST_TYPES[post_type]["tag"]]
limit = TYPE_LIMITS.get(post_type, 4000)
if len(content) > limit:
content = content[: limit - 3] + "..."
if not title:
title = content[:120] + ("..." if len(content) > 120 else "")
# Convert to Ghost lexical format
paragraphs = content.split("\n\n")
children = []
for para in paragraphs:
para = para.strip()
if not para:
continue
children.append(
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": para.replace("\n", " "),
"type": "text",
"version": 1,
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "paragraph",
"version": 1,
}
)
lexical = json.dumps(
{
"root": {
"children": children,
"direction": "ltr",
"format": "",
"indent": 0,
"type": "root",
"version": 1,
}
}
)
payload = {
"posts": [
{
"title": title,
"lexical": lexical,
"status": status,
"visibility": visibility,
"tags": [{"name": t} for t in tags],
"feature_image": PROFILE["avatar"],
}
]
}
session = _get_session_cookie()
if not session:
return {"error": "No Ghost session cookie", "help": "Login at /ghost first"}
try:
cookie_header = session.split("\t")
cookie = f"{cookie_header[5]}={cookie_header[6]}" if len(cookie_header) > 6 else session
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.post(
f"{GHOST_API}/ghost/api/admin/posts/",
headers={"Content-Type": "application/json", "Origin": GHOST_API, "Cookie": cookie},
json=payload,
)
if resp.status_code in (200, 201):
p = resp.json()["posts"][0]
return {
"id": p["id"],
"title": p["title"],
"url": p.get("url", f"{GHOST_API}/{p.get('slug', '')}"),
"status": p["status"],
"created_at": p["created_at"],
"type": post_type,
"tags": [t["name"] for t in p.get("tags", [])],
"feature_image": p.get("feature_image", PROFILE["avatar"]),
"excerpt": p.get("custom_excerpt") or content[:140],
}
elif resp.status_code == 401:
return {"error": "Session expired — re-login at /ghost"}
return {"error": f"Ghost: HTTP {resp.status_code}", "detail": resp.text[:300]}
except Exception as e:
return {"error": str(e)}
async def get_feed(page: int = 1, limit: int = 20, tag: str = "", include: str = "tags,authors") -> dict[str, Any]:
"""Get feed via Ghost Content API."""
if not GHOST_CONTENT_KEY:
return {"posts": [], "error": "content key not configured"}
params = {
"key": GHOST_CONTENT_KEY,
"limit": limit,
"page": page,
"include": include,
"order": "published_at DESC",
"formats": "lexical",
}
if tag:
params["filter"] = f"tag:{tag}"
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(
f"{GHOST_API}/ghost/api/content/posts/",
params=params,
headers={"Accept-Version": "v5.0"},
)
if resp.status_code == 200:
data = resp.json()
posts = []
for p in data.get("posts", []):
ptags = [t["name"] for t in p.get("tags", [])]
post_type = next((t for t in POST_TYPES if POST_TYPES[t]["tag"] in ptags), "article")
posts.append(
{
"id": p["id"],
"title": p["title"],
"excerpt": (p.get("custom_excerpt") or p.get("excerpt") or "")[:200],
"url": p.get("url", ""),
"feature_image": p.get("feature_image", PROFILE["avatar"]),
"created_at": p.get("published_at") or p["created_at"],
"reading_time": p.get("reading_time", 0),
"tags": ptags,
"type": post_type,
"type_label": POST_TYPES.get(post_type, {}).get("label", "Post"),
"type_icon": POST_TYPES.get(post_type, {}).get("icon", "📝"),
}
)
return {
"posts": posts,
"page": page,
"total": data.get("meta", {}).get("pagination", {}).get("total", 0),
"profile": PROFILE,
"post_types": dict(POST_TYPES.items()),
}
return {"posts": [], "error": f"HTTP {resp.status_code}"}
except Exception as e:
return {"posts": [], "error": str(e)}
async def create_thread(posts: list[str], post_type: str = "thread") -> dict[str, Any]:
"""Create a thread — multiple connected posts."""
results = []
for i, content in enumerate(posts):
title = f"Thread {i + 1}/{len(posts)}" if len(posts) > 1 else ""
result = await create_post(content, post_type=post_type, title=title)
results.append(result)
return {
"thread": results,
"total": len(results),
"url": results[0].get("url") if results else None,
}
async def generate_briefing() -> dict[str, Any]:
"""Generate daily intelligence briefing from RAG + news sources."""
content_parts = [f"# 🕵️ Daily Intelligence Briefing\n\n*{datetime.now(UTC).strftime('%B %d, %Y')}*\n"]
# Pull from RAG
try:
from app.rag_service import three_pillar_search
for topic, query in [
("Scam Alerts", "rug pull honeypot scam today"),
("Exploit Watch", "defi exploit hack attack today"),
("Market Intel", "crypto market movement whale alert"),
]:
results = await three_pillar_search(
query=query,
collections=["known_scams", "forensic_reports", "market_intel"],
limit=2,
)
if results.get("results"):
content_parts.append(f"\n## {topic}")
for r in results["results"][:2]:
snippet = (r.get("content") or "")[:300]
if snippet:
content_parts.append(f"- {snippet}")
except Exception:
content_parts.append("\n*Automated briefing — full RAG analysis pending*")
content = "\n".join(content_parts)
return await create_post(
content=content,
post_type="briefing",
title=f"Daily Briefing — {datetime.now(UTC).strftime('%B %d, %Y')}",
)
async def generate_newsletter() -> dict[str, Any]:
"""Generate weekly newsletter with top stories and analysis."""
content_parts = [f"# 📰 Weekly Intelligence Report\n\n*Week of {datetime.now(UTC).strftime('%B %d, %Y')}*\n"]
try:
from app.rag_service import three_pillar_search
for section, query in [
("🔴 Top Threats", "major scam exploit hack this week"),
("🟡 Emerging Patterns", "new scam technique pattern emerging"),
("🟢 Market Overview", "crypto market weekly summary"),
("🔬 Deep Dive", "smart contract vulnerability analysis forensic"),
]:
results = await three_pillar_search(
query=query,
collections=["known_scams", "forensic_reports", "market_intel", "token_analysis"],
limit=3,
)
if results.get("results"):
content_parts.append(f"\n## {section}")
for r in results["results"][:3]:
snippet = (r.get("content") or "")[:400]
if snippet:
content_parts.append(f"- {snippet}")
except Exception:
content_parts.append("\n*Full RAG analysis in next edition*")
content = "\n".join(content_parts)
return await create_post(
content=content,
post_type="newsletter",
title=f"Weekly Intelligence — {datetime.now(UTC).strftime('%B %d, %Y')}",
)