/ server / webhook_receiver.py
webhook_receiver.py
  1  """
  2  Overcast OPML Webhook Receiver
  3  
  4  Receives OPML exports from iOS Shortcut and saves to Sovereign OS data directory.
  5  
  6  Usage:
  7      python webhook_receiver.py
  8  
  9  Then configure your iOS Shortcut to POST to:
 10      http://<your-mac-ip>:5050/overcast
 11  
 12  Find your Mac's IP:
 13      ipconfig getifaddr en0
 14  """
 15  
 16  from flask import Flask, request, jsonify
 17  from datetime import datetime
 18  from pathlib import Path
 19  import xml.etree.ElementTree as ET
 20  
 21  app = Flask(__name__)
 22  
 23  # Configuration
 24  DATA_DIR = Path.home() / 'repos/Sovereign_OS/data/overcast'
 25  PORT = 5050
 26  
 27  
 28  def parse_opml_stats(opml_content: str) -> dict:
 29      """Extract quick stats from OPML for the response."""
 30      try:
 31          root = ET.fromstring(opml_content)
 32          body = root.find('body')
 33  
 34          podcasts = 0
 35          episodes = 0
 36          played = 0
 37  
 38          for outline in body.iter('outline'):
 39              if outline.get('type') == 'rss':
 40                  podcasts += 1
 41              elif outline.get('type') == 'podcast-episode':
 42                  episodes += 1
 43                  if outline.get('played') == '1':
 44                      played += 1
 45  
 46          return {
 47              'podcasts': podcasts,
 48              'episodes': episodes,
 49              'played': played
 50          }
 51      except Exception as e:
 52          return {'error': str(e)}
 53  
 54  
 55  @app.route('/overcast', methods=['POST'])
 56  def receive_overcast():
 57      """Receive OPML from iOS Shortcut."""
 58      opml_content = request.data.decode('utf-8')
 59  
 60      if not opml_content or '<opml' not in opml_content:
 61          return jsonify({
 62              'status': 'error',
 63              'message': 'Invalid OPML content'
 64          }), 400
 65  
 66      # Ensure directory exists
 67      DATA_DIR.mkdir(parents=True, exist_ok=True)
 68  
 69      # Save with timestamp
 70      timestamp = datetime.now().strftime('%Y-%m-%d_%H%M%S')
 71      timestamped_path = DATA_DIR / f'overcast_{timestamp}.opml'
 72      timestamped_path.write_text(opml_content)
 73  
 74      # Also save as latest (always overwritten)
 75      latest_path = DATA_DIR / 'latest.opml'
 76      latest_path.write_text(opml_content)
 77  
 78      # Parse stats
 79      stats = parse_opml_stats(opml_content)
 80  
 81      print(f"[{timestamp}] Received OPML: {len(opml_content):,} bytes")
 82      print(f"           Podcasts: {stats.get('podcasts', '?')}, Episodes: {stats.get('episodes', '?')}, Played: {stats.get('played', '?')}")
 83  
 84      return jsonify({
 85          'status': 'ok',
 86          'bytes': len(opml_content),
 87          'timestamp': timestamp,
 88          'path': str(timestamped_path),
 89          'stats': stats
 90      })
 91  
 92  
 93  @app.route('/health', methods=['GET'])
 94  def health():
 95      """Health check endpoint."""
 96      return jsonify({
 97          'status': 'healthy',
 98          'service': 'Sovereign OS Overcast Webhook',
 99          'data_dir': str(DATA_DIR)
100      })
101  
102  
103  @app.route('/', methods=['GET'])
104  def index():
105      """Landing page with instructions."""
106      return """
107      <html>
108      <head><title>Sovereign OS - Overcast Webhook</title></head>
109      <body style="font-family: system-ui; max-width: 600px; margin: 50px auto; padding: 20px;">
110          <h1>Overcast OPML Webhook</h1>
111          <p>This server receives Overcast OPML exports from iOS Shortcuts.</p>
112  
113          <h2>Endpoints</h2>
114          <ul>
115              <li><code>POST /overcast</code> - Receive OPML data</li>
116              <li><code>GET /health</code> - Health check</li>
117          </ul>
118  
119          <h2>iOS Shortcut Setup</h2>
120          <p>In your Shortcut, use "Get Contents of URL" with:</p>
121          <ul>
122              <li>URL: <code>http://YOUR_MAC_IP:5050/overcast</code></li>
123              <li>Method: POST</li>
124              <li>Request Body: The OPML file content</li>
125          </ul>
126  
127          <p><a href="/health">Check Health</a></p>
128      </body>
129      </html>
130      """
131  
132  
133  if __name__ == '__main__':
134      print(f"""
135  ╔═══════════════════════════════════════════════════════════╗
136  ║          Sovereign OS - Overcast Webhook Receiver         ║
137  ╠═══════════════════════════════════════════════════════════╣
138  ║  Server running on port {PORT}                              ║
139  ║  Data directory: {str(DATA_DIR):<40} ║
140  ║                                                           ║
141  ║  Find your Mac's IP:                                      ║
142  ║    ipconfig getifaddr en0                                 ║
143  ║                                                           ║
144  ║  Configure iOS Shortcut to POST to:                       ║
145  ║    http://<your-ip>:{PORT}/overcast                         ║
146  ╚═══════════════════════════════════════════════════════════╝
147  """)
148      app.run(host='0.0.0.0', port=PORT, debug=False)