| """ |
| π‘ Haven Recipe Cast System |
| Cast recipes to mobile devices via local network. |
| """ |
|
|
| import threading |
| import io |
| import json |
| from http.server import HTTPServer, BaseHTTPRequestHandler |
|
|
| |
| _current_recipe = None |
| _server_running = False |
|
|
|
|
| class RecipeHandler(BaseHTTPRequestHandler): |
| def do_GET(self): |
| global _current_recipe |
| self.send_response(200) |
| self.send_header('Content-type', 'text/html; charset=utf-8') |
| self.send_header('Access-Control-Allow-Origin', '*') |
| self.end_headers() |
| |
| if _current_recipe: |
| title = _current_recipe.get('title', 'Recipe') |
| html = f""" |
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>{title}</title> |
| <style> |
| body {{ font-family: Georgia, serif; max-width: 600px; margin: 0 auto; padding: 20px; background: #FFF8F0; }} |
| h1 {{ color: #C07A5C; }} |
| .ingredients {{ background: #D4E6D5; padding: 15px; border-radius: 10px; margin: 20px 0; }} |
| .step {{ background: white; padding: 15px; margin: 10px 0; border-radius: 10px; border-left: 4px solid #C07A5C; }} |
| </style> |
| </head> |
| <body> |
| <h1>π©π»βπ³ {title}</h1> |
| <div class="ingredients"> |
| <h3>Ingredients</h3> |
| <ul>{''.join(f'<li>{i}</li>' for i in _current_recipe.get('ingredients', []))}</ul> |
| </div> |
| <h3>Instructions</h3> |
| {''.join(f'<div class="step"><strong>Step {i+1}:</strong> {s}</div>' for i, s in enumerate(_current_recipe.get('steps', [])))} |
| </body> |
| </html> |
| """ |
| else: |
| html = "<!DOCTYPE html><html><head><meta charset='UTF-8'></head><body><h1>No recipe loaded</h1></body></html>" |
| |
| self.wfile.write(html.encode('utf-8')) |
| |
| def log_message(self, format, *args): |
| pass |
|
|
|
|
| def start_cast_server(port=8505): |
| global _server_running |
| if _server_running: |
| return |
| |
| def run_server(): |
| try: |
| server = HTTPServer(('0.0.0.0', port), RecipeHandler) |
| server.serve_forever() |
| except Exception: |
| pass |
| |
| thread = threading.Thread(target=run_server, daemon=True) |
| thread.start() |
| _server_running = True |
|
|
|
|
| def cast_recipe(recipe_dict): |
| """Cast a recipe to the server and return the URL.""" |
| global _current_recipe |
| _current_recipe = recipe_dict |
| return get_cast_url() |
|
|
|
|
| def get_cast_url(): |
| """Get the cast URL using the machine's local IP address.""" |
| import socket |
| try: |
| s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| s.connect(("8.8.8.8", 80)) |
| local_ip = s.getsockname()[0] |
| s.close() |
| return f"http://{local_ip}:8505" |
| except: |
| return "http://localhost:8505" |
|
|
|
|
| def generate_qr_code(url): |
| """Generate QR code for recipe URL.""" |
| try: |
| import qrcode |
| qr = qrcode.QRCode(version=1, box_size=10, border=2) |
| qr.add_data(url) |
| qr.make(fit=True) |
| img = qr.make_image(fill_color="black", back_color="white") |
| |
| buffer = io.BytesIO() |
| img.save(buffer, format='PNG') |
| buffer.seek(0) |
| return buffer.getvalue() |
| except Exception: |
| return None |
|
|