Update inline CSS within `HTML_TEMPLATE` and the root endpoint's HTML. Double curly braces `{{` and `}}` are now used for CSS rules to prevent the templating engine from misinterpreting single curly braces `{}` as placeholders, ensuring correct CSS rendering.
279 lines
8.6 KiB
Python
279 lines
8.6 KiB
Python
import os
|
|
import sys
|
|
import subprocess
|
|
import time
|
|
import signal
|
|
import atexit
|
|
import threading
|
|
import logging
|
|
import uvicorn
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import HTMLResponse
|
|
|
|
# Configure logging
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s:%(name)s: %(message)s')
|
|
log = logging.getLogger(__name__)
|
|
|
|
# Try to import markdown, but provide a fallback if it's not available
|
|
try:
|
|
import markdown
|
|
import markdown.extensions.fenced_code
|
|
import markdown.extensions.tables
|
|
MARKDOWN_AVAILABLE = True
|
|
except ImportError:
|
|
MARKDOWN_AVAILABLE = False
|
|
log.warning("markdown package not available. Will serve raw markdown files.")
|
|
|
|
# Create the FastAPI app
|
|
app = FastAPI(title="Markdown Server", docs_url=None, redoc_url=None)
|
|
|
|
# Define the HTML template for rendering markdown
|
|
# Using double curly braces to escape them in the CSS
|
|
HTML_TEMPLATE = """
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{title}</title>
|
|
<style>
|
|
body {{
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
line-height: 1.6;
|
|
color: #333;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}}
|
|
h1 {{
|
|
border-bottom: 2px solid #eaecef;
|
|
padding-bottom: 0.3em;
|
|
color: #24292e;
|
|
}}
|
|
h2 {{
|
|
border-bottom: 1px solid #eaecef;
|
|
padding-bottom: 0.3em;
|
|
color: #24292e;
|
|
}}
|
|
code {{
|
|
background-color: #f6f8fa;
|
|
padding: 0.2em 0.4em;
|
|
border-radius: 3px;
|
|
font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
|
|
}}
|
|
pre {{
|
|
background-color: #f6f8fa;
|
|
padding: 16px;
|
|
border-radius: 6px;
|
|
overflow: auto;
|
|
}}
|
|
pre code {{
|
|
background-color: transparent;
|
|
padding: 0;
|
|
}}
|
|
a {{
|
|
color: #0366d6;
|
|
text-decoration: none;
|
|
}}
|
|
a:hover {{
|
|
text-decoration: underline;
|
|
}}
|
|
table {{
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
margin-bottom: 16px;
|
|
}}
|
|
table, th, td {{
|
|
border: 1px solid #dfe2e5;
|
|
}}
|
|
th, td {{
|
|
padding: 8px 16px;
|
|
text-align: left;
|
|
}}
|
|
th {{
|
|
background-color: #f6f8fa;
|
|
}}
|
|
tr:nth-child(even) {{
|
|
background-color: #f6f8fa;
|
|
}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
{content}
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Function to read and convert markdown to HTML
|
|
def render_markdown(file_path, title):
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
md_content = f.read()
|
|
|
|
if MARKDOWN_AVAILABLE:
|
|
# Convert markdown to HTML with extensions
|
|
html_content = markdown.markdown(
|
|
md_content,
|
|
extensions=[
|
|
'markdown.extensions.fenced_code',
|
|
'markdown.extensions.tables',
|
|
'markdown.extensions.toc'
|
|
]
|
|
)
|
|
else:
|
|
# Simple fallback if markdown package is not available
|
|
# Just wrap the content in <pre> tags to preserve formatting
|
|
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)
|
|
except Exception as e:
|
|
return HTML_TEMPLATE.format(
|
|
title="Error",
|
|
content=f"<h1>Error</h1><p>Failed to render markdown: {str(e)}</p>"
|
|
)
|
|
|
|
# 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")
|
|
|
|
@app.get("/privacy", response_class=HTMLResponse)
|
|
async def get_privacy(request: Request):
|
|
return render_markdown("PRIVACY_POLICY.md", "Privacy Policy")
|
|
|
|
# Root route that redirects to TOS
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def root(request: Request):
|
|
return """
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Bot Legal Documents</title>
|
|
<style>
|
|
body {{
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
line-height: 1.6;
|
|
color: #333;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
text-align: center;
|
|
}}
|
|
h1 {{
|
|
margin-bottom: 30px;
|
|
}}
|
|
.links {{
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 20px;
|
|
margin-top: 30px;
|
|
}}
|
|
.link-button {{
|
|
display: inline-block;
|
|
padding: 10px 20px;
|
|
background-color: #0366d6;
|
|
color: white;
|
|
text-decoration: none;
|
|
border-radius: 4px;
|
|
font-weight: bold;
|
|
}}
|
|
.link-button:hover {{
|
|
background-color: #0056b3;
|
|
}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Discord Bot Legal Documents</h1>
|
|
<p>Please review our Terms of Service and Privacy Policy.</p>
|
|
<div class="links">
|
|
<a href="/tos" class="link-button">Terms of Service</a>
|
|
<a href="/privacy" class="link-button">Privacy Policy</a>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Function to start the server in a thread
|
|
def start_markdown_server_in_thread(host="0.0.0.0", port=5006):
|
|
"""Start the markdown server in a separate thread."""
|
|
log.info(f"Starting markdown server on {host}:{port}...")
|
|
|
|
def run_server():
|
|
try:
|
|
uvicorn.run(app, host=host, port=port)
|
|
except Exception as e:
|
|
log.exception(f"Error running markdown server: {e}")
|
|
|
|
# Start the server in a daemon thread
|
|
server_thread = threading.Thread(target=run_server, daemon=True)
|
|
server_thread.start()
|
|
log.info(f"Markdown server thread started. TOS available at: http://{host}:{port}/tos")
|
|
|
|
return server_thread
|
|
|
|
def start_server():
|
|
"""Start the markdown server as a background process (legacy method)."""
|
|
print("Starting markdown server on port 5006...")
|
|
|
|
# Get the directory of this script
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# Start the server as a subprocess
|
|
server_process = subprocess.Popen(
|
|
[sys.executable, os.path.join(script_dir, "markdown_server.py")],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True
|
|
)
|
|
|
|
# Register a function to terminate the server when this script exits
|
|
def cleanup():
|
|
if server_process.poll() is None: # If process is still running
|
|
print("Stopping markdown server...")
|
|
server_process.terminate()
|
|
try:
|
|
server_process.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
print("Server didn't terminate gracefully, forcing...")
|
|
server_process.kill()
|
|
|
|
atexit.register(cleanup)
|
|
|
|
# Handle signals
|
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
signal.signal(sig, lambda signum, frame: sys.exit(0))
|
|
|
|
# Wait a moment for the server to start
|
|
time.sleep(2)
|
|
|
|
# Check if the server started successfully
|
|
if server_process.poll() is not None:
|
|
print("Failed to start server. Exit code:", server_process.returncode)
|
|
output, _ = server_process.communicate()
|
|
print("Server output:", output)
|
|
return False
|
|
|
|
print(f"Markdown server running on http://localhost:5006")
|
|
print("TOS available at: http://localhost:5006/tos")
|
|
print("Privacy Policy available at: http://localhost:5006/privacy")
|
|
|
|
return True
|
|
|
|
def run_as_daemon():
|
|
"""Run the server as a daemon process."""
|
|
if start_server():
|
|
# Keep the script running to maintain the server
|
|
try:
|
|
while True:
|
|
time.sleep(60) # Sleep to reduce CPU usage
|
|
except KeyboardInterrupt:
|
|
print("Received keyboard interrupt. Shutting down...")
|
|
sys.exit(0)
|
|
|
|
if __name__ == "__main__":
|
|
# If run directly, start the server in the main thread
|
|
log.info("Starting markdown server on port 5006...")
|
|
uvicorn.run(app, host="0.0.0.0", port=5006)
|