# app.py - Fixed URL Shortening with spoo.me from flask import Flask, request, jsonify, send_from_directory, make_response import google.generativeai as genai from dotenv import load_dotenv import os from flask_cors import CORS import markdown2 import re import requests from gtts import gTTS import uuid # Load environment variables load_dotenv() # Configure paths AUDIO_FOLDER = os.path.join('static', 'audio') os.makedirs(AUDIO_FOLDER, exist_ok=True) app = Flask(__name__, static_folder='static') CORS(app) # AI Configuration system_instruction = """ You are a helpful AI assistant named Athspi. When responding: 1. Never mention "audio" or technical terms 2. For responses that would benefit from audio (like stories, explanations, or content meant to be heard), include the audio version between these markers: [AUDIO]content here[/AUDIO] 3. Keep responses natural and friendly 4. Decide automatically when to include audio based on the content type 5. For stories, always include audio version 6. If a URL is present in the user's message, respond with: [SHORTEN]original_url|short_url[/SHORTEN] Example: I've shortened your link: [SHORTEN]https://example.com|https://spoo.me/abc123[/SHORTEN] """ genai.configure(api_key=os.getenv("GEMINI_API_KEY")) model = genai.GenerativeModel('gemini-2.5-flasho', system_instruction=system_instruction) # In-memory session storage chat_sessions = {} def convert_markdown_to_html(text): html = markdown2.markdown(text, extras=["fenced-code-blocks", "tables"]) html = re.sub(r'
', r'
', html)
    return html

def process_response(full_response):
    """Extract visible text, audio content, and short links"""
    audio_match = re.search(r'\[AUDIO\](.*?)\[/AUDIO\]', full_response, re.DOTALL)
    audio_content = audio_match.group(1).strip() if audio_match else None
    visible_text = re.sub(r'\[/?AUDIO\]', '', full_response)

    short_link_match = re.search(r'\[SHORTEN\](.*?)\|([^|]*?)\[/SHORTEN\]', visible_text, re.DOTALL)
    if short_link_match:
        original_url = short_link_match.group(1).strip()
        short_url = short_link_match.group(2).strip()
        link_html = f'

🔗 Original: {original_url}

' \ f'

✂️ Shortened: {short_url}

' visible_text = re.sub(r'\[SHORTEN\].*?\[/SHORTEN\]', link_html, visible_text) return visible_text, audio_content def generate_audio(text): """Generate audio file from text""" clean_text = re.sub(r'[^\w\s.,!?\-]', '', text) filename = f"audio_{uuid.uuid4()}.mp3" filepath = os.path.join(AUDIO_FOLDER, filename) try: tts = gTTS(text=clean_text, lang='en', slow=False) tts.save(filepath) return filename except Exception as e: print("TTS Error:", str(e)) return None def shorten_url_with_spoo_me(url): """Shorten URL using spoo.me API with correct headers""" try: # Ensure URL has scheme if not url.startswith("http"): url = "https://" + url payload = { "url": url # alias can be added: "alias": "mycustom" } headers = { "Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded" } response = requests.post( "https://spoo.me/", data=payload, # This sends as form-encoded headers=headers, timeout=10 ) print("spoo.me Status:", response.status_code) # Debug print("spoo.me Response:", response.text) # Debug if response.status_code == 200: result = response.json() short_url = result.get("shorturl") or result.get("url") if short_url and short_url.startswith("http"): return short_url print("Failed to shorten:", response.status_code, response.text) return None except Exception as e: print("Request Exception:", str(e)) return None @app.route('/chat', methods=['POST']) def chat(): try: data = request.json user_message = data.get('message', '').strip() session_id = request.cookies.get('session_id') or str(uuid.uuid4()) if not user_message: return jsonify({"error": "Message required"}), 400 # Get or create chat session if session_id not in chat_sessions: chat_sessions[session_id] = model.start_chat(history=[]) chat_session = chat_sessions[session_id] # Send to AI first response = chat_session.send_message(user_message) full_text = response.text.strip() # Extract URL and shorten url_match = re.search(r'https?://[^\s<>"{}|\\^`\[\]]+', user_message) if url_match: original_url = url_match.group(0) short_url = shorten_url_with_spoo_me(original_url) if short_url: short_tag = f"[SHORTEN]{original_url}|{short_url}[/SHORTEN]" if "[SHORTEN]" not in full_text: full_text += f"\n\n{short_tag}" else: # Optional: Inform user # full_text += "\n\nI couldn't shorten the link right now." pass # Silent fallback # Process final response visible_text, audio_content = process_response(full_text) html_response = convert_markdown_to_html(visible_text) result = { "response_html": html_response, "has_audio": False } if audio_content: audio_filename = generate_audio(audio_content) if audio_filename: result["audio_filename"] = audio_filename result["has_audio"] = True # Return response with session cookie resp = make_response(jsonify(result)) resp.set_cookie('session_id', session_id, max_age=3600, httponly=True, samesite='Lax') return resp except Exception as e: print("Server Error:", str(e)) return jsonify({"error": "Something went wrong. Please try again."}), 500 @app.route('/new-chat', methods=['POST']) def new_chat(): session_id = str(uuid.uuid4()) resp = make_response(jsonify({"status": "new chat started"})) resp.set_cookie('session_id', session_id, max_age=3600, httponly=True, samesite='Lax') return resp @app.route('/download/') def download_audio(filename): try: return send_from_directory(AUDIO_FOLDER, filename, as_attachment=True) except FileNotFoundError: return jsonify({"error": "Audio file not found"}), 404 @app.route('/') def serve_index(): return send_from_directory('static', 'index.html') @app.route('/') def serve_static(path): return send_from_directory('static', path) if __name__ == '__main__': app.run(host="0.0.0.0", port=7860)