Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Standalone Horoscope API Server | |
| This script runs the horoscope system without database dependencies | |
| """ | |
| import os | |
| import sys | |
| import logging | |
| from flask import Flask, jsonify, request, render_template_string | |
| from flask_cors import CORS | |
| import json | |
| from datetime import datetime, date | |
| import requests | |
| from bs4 import BeautifulSoup | |
| import trafilatura | |
| import time | |
| # Set up logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Create Flask app | |
| app = Flask(__name__) | |
| app.secret_key = os.environ.get("SESSION_SECRET", "dev_secret_key") | |
| CORS(app) | |
| # Zodiac signs | |
| ZODIAC_SIGNS = [ | |
| "aries", "taurus", "gemini", "cancer", | |
| "leo", "virgo", "libra", "scorpio", | |
| "sagittarius", "capricorn", "aquarius", "pisces" | |
| ] | |
| class HoroscopeScraper: | |
| """Base horoscope scraper""" | |
| def __init__(self, timeout=30): | |
| self.timeout = timeout | |
| def fetch_url(self, url): | |
| """Fetch content from URL""" | |
| try: | |
| response = requests.get(url, timeout=self.timeout) | |
| response.raise_for_status() | |
| return response.text | |
| except Exception as e: | |
| logger.error(f"Error fetching {url}: {str(e)}") | |
| return None | |
| def extract_text(self, html_content): | |
| """Extract main text content from HTML""" | |
| try: | |
| text = trafilatura.extract(html_content) | |
| return text if text else "" | |
| except Exception as e: | |
| logger.error(f"Error extracting text: {str(e)}") | |
| return "" | |
| class AstrologyComScraper(HoroscopeScraper): | |
| """Scraper for Astrology.com""" | |
| def __init__(self): | |
| super().__init__() | |
| self.base_url = "https://www.astrology.com" | |
| def scrape_sign(self, base_url, sign, date_str=None): | |
| """Scrape horoscope for a specific sign""" | |
| try: | |
| # Format URL for astrology.com | |
| url = f"{base_url}/horoscope/daily/{sign}" | |
| html_content = self.fetch_url(url) | |
| if not html_content: | |
| return {"success": False, "error": "Failed to fetch content"} | |
| text_content = self.extract_text(html_content) | |
| if not text_content: | |
| return {"success": False, "error": "Failed to extract text"} | |
| # Parse with BeautifulSoup for better extraction | |
| soup = BeautifulSoup(html_content, 'html.parser') | |
| # Extract horoscope prediction | |
| prediction = self._extract_prediction(soup, text_content) | |
| if not prediction: | |
| return {"success": False, "error": "Could not find horoscope prediction"} | |
| return { | |
| "success": True, | |
| "sign": sign, | |
| "prediction": prediction, | |
| "date": date.today().isoformat(), | |
| "source": "astrology.com", | |
| "url": url | |
| } | |
| except Exception as e: | |
| logger.error(f"Error scraping {sign} from astrology.com: {str(e)}") | |
| return {"success": False, "error": str(e)} | |
| def _extract_prediction(self, soup, text_content): | |
| """Extract horoscope prediction from astrology.com""" | |
| # Try multiple selectors | |
| selectors = [ | |
| '.horoscope-content', | |
| '.daily-horoscope', | |
| 'div[data-testid="horoscope-content"]', | |
| '.horoscope-text' | |
| ] | |
| for selector in selectors: | |
| element = soup.select_one(selector) | |
| if element: | |
| return element.get_text().strip() | |
| # Fallback: extract from text content | |
| lines = text_content.split('\n') | |
| for i, line in enumerate(lines): | |
| if any(word in line.lower() for word in ['today', 'daily', 'horoscope']): | |
| # Return the next few lines as prediction | |
| prediction_lines = lines[i:i+5] | |
| return ' '.join(prediction_lines).strip() | |
| # Last resort: return first substantial paragraph | |
| paragraphs = [p.strip() for p in text_content.split('\n') if len(p.strip()) > 50] | |
| return paragraphs[0] if paragraphs else text_content[:300] | |
| class HoroscopeComScraper(HoroscopeScraper): | |
| """Scraper for Horoscope.com""" | |
| def __init__(self): | |
| super().__init__() | |
| self.base_url = "https://www.horoscope.com" | |
| def scrape_sign(self, base_url, sign, date_str=None): | |
| """Scrape horoscope for a specific sign""" | |
| try: | |
| # Map sign to horoscope.com format | |
| sign_map = { | |
| "aries": 1, "taurus": 2, "gemini": 3, "cancer": 4, | |
| "leo": 5, "virgo": 6, "libra": 7, "scorpio": 8, | |
| "sagittarius": 9, "capricorn": 10, "aquarius": 11, "pisces": 12 | |
| } | |
| sign_id = sign_map.get(sign.lower()) | |
| if not sign_id: | |
| return {"success": False, "error": f"Invalid sign: {sign}"} | |
| url = f"{base_url}/us/horoscopes/general/horoscope-general-daily-today.aspx?sign={sign_id}" | |
| html_content = self.fetch_url(url) | |
| if not html_content: | |
| return {"success": False, "error": "Failed to fetch content"} | |
| text_content = self.extract_text(html_content) | |
| if not text_content: | |
| return {"success": False, "error": "Failed to extract text"} | |
| # Parse with BeautifulSoup | |
| soup = BeautifulSoup(html_content, 'html.parser') | |
| # Extract horoscope prediction | |
| prediction = self._extract_prediction(soup, text_content) | |
| if not prediction: | |
| return {"success": False, "error": "Could not find horoscope prediction"} | |
| return { | |
| "success": True, | |
| "sign": sign, | |
| "prediction": prediction, | |
| "date": date.today().isoformat(), | |
| "source": "horoscope.com", | |
| "url": url | |
| } | |
| except Exception as e: | |
| logger.error(f"Error scraping {sign} from horoscope.com: {str(e)}") | |
| return {"success": False, "error": str(e)} | |
| def _extract_prediction(self, soup, text_content): | |
| """Extract horoscope prediction from horoscope.com""" | |
| # Try multiple selectors | |
| selectors = [ | |
| '.horoscope-content', | |
| '.main-horoscope', | |
| '#DailyHoroscope', | |
| '.horoscope-text' | |
| ] | |
| for selector in selectors: | |
| element = soup.select_one(selector) | |
| if element: | |
| return element.get_text().strip() | |
| # Fallback: extract meaningful content from text | |
| lines = text_content.split('\n') | |
| prediction_lines = [] | |
| for line in lines: | |
| line = line.strip() | |
| if len(line) > 30 and not any(skip in line.lower() for skip in ['cookie', 'privacy', 'subscribe', 'newsletter']): | |
| prediction_lines.append(line) | |
| if len(prediction_lines) >= 3: | |
| break | |
| return ' '.join(prediction_lines) if prediction_lines else text_content[:300] | |
| # Initialize scrapers | |
| scrapers = { | |
| "astrology.com": AstrologyComScraper(), | |
| "horoscope.com": HoroscopeComScraper(), | |
| } | |
| # HTML Template | |
| HTML_TEMPLATE = ''' | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI-Powered Daily Horoscopes</title> | |
| <link href="https://cdn.replit.com/agent/bootstrap-agent-dark-theme.min.css" rel="stylesheet"> | |
| <style> | |
| .horoscope-card { | |
| background: var(--bs-dark); | |
| border: 1px solid var(--bs-secondary); | |
| border-radius: 8px; | |
| padding: 1.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| .sign-badge { | |
| background: var(--bs-primary); | |
| color: white; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 15px; | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| } | |
| .prediction-text { | |
| line-height: 1.6; | |
| margin-top: 1rem; | |
| } | |
| .loading { | |
| display: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container mt-4"> | |
| <div class="row"> | |
| <div class="col-12"> | |
| <h1 class="text-center mb-4">🌟 AI-Powered Daily Horoscopes</h1> | |
| <p class="text-center text-muted mb-5">Ready to integrate with astroastayogini.in</p> | |
| </div> | |
| </div> | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h5>Test Single Horoscope</h5> | |
| </div> | |
| <div class="card-body"> | |
| <form id="testForm"> | |
| <div class="mb-3"> | |
| <label for="zodiacSign" class="form-label">Zodiac Sign</label> | |
| <select class="form-select" id="zodiacSign" required> | |
| <option value="">Select your sign</option> | |
| <option value="aries">Aries</option> | |
| <option value="taurus">Taurus</option> | |
| <option value="gemini">Gemini</option> | |
| <option value="cancer">Cancer</option> | |
| <option value="leo">Leo</option> | |
| <option value="virgo">Virgo</option> | |
| <option value="libra">Libra</option> | |
| <option value="scorpio">Scorpio</option> | |
| <option value="sagittarius">Sagittarius</option> | |
| <option value="capricorn">Capricorn</option> | |
| <option value="aquarius">Aquarius</option> | |
| <option value="pisces">Pisces</option> | |
| </select> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="source" class="form-label">Source</label> | |
| <select class="form-select" id="source"> | |
| <option value="astrology.com">Astrology.com</option> | |
| <option value="horoscope.com">Horoscope.com</option> | |
| </select> | |
| </div> | |
| <button type="submit" class="btn btn-primary">Get Horoscope</button> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-6"> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h5>Integration Status</h5> | |
| </div> | |
| <div class="card-body"> | |
| <div class="mb-3"> | |
| <strong>Scrapers:</strong> ✅ Astrology.com, ✅ Horoscope.com | |
| </div> | |
| <div class="mb-3"> | |
| <strong>OpenAI:</strong> {{ '✅' if openai_configured else '❌' }} {{ 'Configured' if openai_configured else 'Needs API Key' }} | |
| </div> | |
| <div class="mb-3"> | |
| <strong>WordPress:</strong> {{ '✅' if wordpress_configured else '❌' }} {{ 'Ready for astroastayogini.in' if wordpress_configured else 'Needs Configuration' }} | |
| </div> | |
| <p class="text-muted mt-3">Ready to integrate with your website at astroastayogini.in</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="row mt-4"> | |
| <div class="col-12"> | |
| <div id="results"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.getElementById('testForm').addEventListener('submit', async function(e) { | |
| e.preventDefault(); | |
| const sign = document.getElementById('zodiacSign').value; | |
| const source = document.getElementById('source').value; | |
| if (!sign) { | |
| alert('Please select a zodiac sign'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/api/test', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| sign: sign, | |
| source: source | |
| }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| document.getElementById('results').innerHTML = ` | |
| <div class="horoscope-card"> | |
| <div class="d-flex justify-content-between align-items-center mb-3"> | |
| <span class="sign-badge">${data.sign.toUpperCase()}</span> | |
| <small>Source: ${data.source}</small> | |
| </div> | |
| <div class="prediction-text">${data.prediction}</div> | |
| <small class="text-muted">Date: ${data.date}</small> | |
| </div> | |
| `; | |
| } else { | |
| document.getElementById('results').innerHTML = ` | |
| <div class="alert alert-danger">Error: ${data.error}</div> | |
| `; | |
| } | |
| } catch (error) { | |
| document.getElementById('results').innerHTML = ` | |
| <div class="alert alert-danger">Network error: ${error.message}</div> | |
| `; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| ''' | |
| def index(): | |
| """Main page""" | |
| openai_configured = bool(os.environ.get("OPENAI_API_KEY")) | |
| wordpress_configured = bool(os.environ.get("WORDPRESS_API_URL")) | |
| return render_template_string( | |
| HTML_TEMPLATE, | |
| openai_configured=openai_configured, | |
| wordpress_configured=wordpress_configured | |
| ) | |
| def health_check(): | |
| """Health check endpoint""" | |
| return jsonify({ | |
| "status": "ok", | |
| "message": "Horoscope API is running", | |
| "scrapers": list(scrapers.keys()), | |
| "openai_configured": bool(os.environ.get("OPENAI_API_KEY")), | |
| "wordpress_configured": bool(os.environ.get("WORDPRESS_API_URL")) | |
| }) | |
| def test_scrape(): | |
| """Test horoscope scraping""" | |
| data = request.get_json() | |
| if not data: | |
| return jsonify({"error": "Missing request data"}), 400 | |
| sign = data.get('sign', '').lower() | |
| source = data.get('source', 'astrology.com') | |
| if not sign: | |
| return jsonify({"error": "Missing 'sign' parameter"}), 400 | |
| if sign not in ZODIAC_SIGNS: | |
| return jsonify({"error": f"Invalid zodiac sign: {sign}"}), 400 | |
| if source not in scrapers: | |
| return jsonify({"error": f"Unknown source: {source}"}), 400 | |
| try: | |
| scraper = scrapers[source] | |
| result = scraper.scrape_sign(scraper.base_url, sign) | |
| return jsonify(result) | |
| except Exception as e: | |
| logger.error(f"Error testing scrape: {str(e)}") | |
| return jsonify({"success": False, "error": str(e)}), 500 | |
| def configure_wordpress(): | |
| """Configure WordPress integration for astroastayogini.in""" | |
| data = request.get_json() | |
| if not data: | |
| return jsonify({"error": "Missing configuration data"}), 400 | |
| # This would typically save to environment or config file | |
| # For now, just validate the provided configuration | |
| required_fields = ['api_url', 'username', 'password'] | |
| missing_fields = [field for field in required_fields if not data.get(field)] | |
| if missing_fields: | |
| return jsonify({ | |
| "error": f"Missing required fields: {', '.join(missing_fields)}" | |
| }), 400 | |
| return jsonify({ | |
| "success": True, | |
| "message": "WordPress configuration received. In production, this would be securely stored." | |
| }) | |
| if __name__ == "__main__": | |
| logger.info("Starting Horoscope API for astroastayogini.in integration") | |
| app.run(host="0.0.0.0", port=5000, debug=True) |