Home Documentation Developer Guides

Developer & Integration Guides

Service script conventions, notification setup, and external panel integration notes for advanced deployments.

Creating Compatible Service Scripts

xOTT Panel uses universal service scripts to connect live channels, events, VOD, manifests, and account actions through one consistent interface. This guide summarizes the structure expected by the panel when adding or maintaining service integrations.

Script Structure

All service scripts must follow this standardized format:

#!/usr/bin/env python3
"""
Service Script Template - Standardized Format
=============================================

This script provides a standardized interface for service integration.
It follows the platform's service script conventions for seamless integration.

Required Actions:
- login: Authenticate user and store tokens
- refresh: Refresh authentication tokens
- channels: Get available channels list
- events: Get live and upcoming events list
- manifest: Get stream manifest URL
- cdm: Get DRM decryption keys
- vod_categories: Get VOD content categories
- vod_content: Get VOD content for a category
- vod_seasons: Get seasons for a series
- vod_episodes: Get episodes for a season
- vod_manifest: Get VOD manifest URL
- vod_cdm: Get DRM decryption keys for VOD

Standard Parameters:
- user: Username for authentication
- password: Password for authentication
- action: Action to perform
- id: Channel/Video ID for manifest/cdm actions
- category: VOD category
- page: Page number for pagination (default: 1)
- limit: Items per page (default: 20)
- proxy: Optional proxy URL
"""

import sys
import os
import json
import base64
import time
import uuid
import requests
import secrets
import string
from bs4 import BeautifulSoup
from pywidevine.cdm import Cdm
from pywidevine.device import Device
from pywidevine.pssh import PSSH

# ============================================================================
# CONFIGURATION
# ============================================================================

# Service Configuration
SERVICE_NAME = "YourService"
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))

# Global variables (will be set during execution)
user = None
password = None
action = None
id = None
proxy = None
req = None

# ============================================================================
# LOGGING
# ============================================================================

def log_info(message):
    """Log info message to stderr."""
    print(message, file=sys.stderr)

def log_error(message):
    """Log error message to stderr."""
    print(f"ERROR: {message}", file=sys.stderr)

# ============================================================================
# DRM/CDM FUNCTIONS
# ============================================================================

def do_cdm(pssh_data, license_url, token=None):
    """Get DRM decryption keys using Widevine CDM."""
    try:
        if not license_url:
            log_error("No license URL provided (DRM not enabled for this stream)")
            return None

        pssh_obj = PSSH(pssh_data)
        device = Device.load(os.path.join(os.path.dirname(__file__), 'WVD.wvd'))
        cdm = Cdm.from_device(device)
        session_id = cdm.open()
        challenge = cdm.get_license_challenge(session_id, pssh_obj)

        headers = {
            'content-type': 'application/octet-stream',
            'origin': 'https://your-service.com',
            'referer': 'https://your-service.com/',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        }
        
        if token:
            headers['authorization'] = f'Bearer {token}'

        license_response = req.post(license_url, headers=headers, data=challenge)
        
        if license_response.ok:
            cdm.parse_license(session_id, license_response.content)
            
            keys = []
            for key in cdm.get_keys(session_id):
                if key.type != 'SIGNING':
                    keys.append(f"{key.kid.hex}:{key.key.hex()}")
            
            cdm.close(session_id)
            return keys
        else:
            log_error("License request failed")
            return None
                    
    except Exception as e:
        log_error(f"CDM processing failed: {str(e)}")
        return None

# ============================================================================
# AUTHENTICATION
# ============================================================================

def do_login(username, password):
    """Handle login action."""
    try:
        # Your authentication logic here
        # Return authentication tokens or session data
        pass
    except Exception as e:
        log_error(f"Login failed: {str(e)}")
        return None

def do_refresh(token):
    """Handle token refresh action."""
    try:
        # Your token refresh logic here
        pass
    except Exception as e:
        log_error(f"Token refresh failed: {str(e)}")
        return None

# ============================================================================
# CONTENT RETRIEVAL
# ============================================================================

def get_channels(token):
    """Get available channels list."""
    try:
        # Your channels retrieval logic here
        # Return formatted channels data
        pass
    except Exception as e:
        log_error(f"Failed to get channels: {str(e)}")
        return None

def get_events(token):
    """Get available events list."""
    try:
        # Your events retrieval logic here
        # Return formatted events data
        pass
    except Exception as e:
        log_error(f"Failed to get events: {str(e)}")
        return None

def get_vod_categories(token):
    """Get VOD content categories."""
    try:
        # Your VOD categories logic here
        pass
    except Exception as e:
        log_error(f"Failed to get VOD categories: {str(e)}")
        return None

def get_vod_content(token, category, page=1, limit=20):
    """Get VOD content for a category."""
    try:
        # Your VOD content logic here
        pass
    except Exception as e:
        log_error(f"Failed to get VOD content: {str(e)}")
        return None

# ============================================================================
# STREAMING
# ============================================================================

def get_manifest(token, channel_id):
    """Get stream manifest URL."""
    try:
        # Your manifest retrieval logic here
        # Return manifest URL and DRM info if applicable
        pass
    except Exception as e:
        log_error(f"Failed to get manifest: {str(e)}")
        return None

def get_vod_manifest(token, content_id):
    """Get VOD manifest URL."""
    try:
        # Your VOD manifest logic here
        pass
    except Exception as e:
        log_error(f"Failed to get VOD manifest: {str(e)}")
        return None

# ============================================================================
# MAIN EXECUTION
# ============================================================================

def main():
    global user, password, action, id, proxy, req
    
    # Parse command line arguments
    args = sys.argv[1:]
    for i in range(0, len(args), 2):
        if i + 1 < len(args):
            key = args[i].replace('--', '')
            value = args[i + 1]
            
            if key == 'user':
                user = value
            elif key == 'password':
                password = value
            elif key == 'action':
                action = value
            elif key == 'id':
                id = value
            elif key == 'proxy':
                proxy = value
    
    # Create requests session with proxy if provided
    req = requests.Session()
    if proxy:
        req.proxies = {'http': proxy, 'https': proxy}
    
    # Execute action based on input
    try:
        if action == 'login':
            result = do_login(user, password)
        elif action == 'refresh':
            result = do_refresh(user)  # user contains token in refresh action
        elif action == 'channels':
            result = get_channels(user)  # user contains token in channels action
        elif action == 'events':
            result = get_events(user)  # user contains token in events action
        elif action == 'manifest':
            result = get_manifest(user, id)  # user contains token, id contains channel_id
        elif action == 'cdm':
            result = do_cdm(user, password, id)  # user contains pssh, password contains license_url, id contains token
        elif action == 'vod_categories':
            result = get_vod_categories(user)  # user contains token
        elif action == 'vod_content':
            result = get_vod_content(user, password, id)  # user contains token, password contains category, id contains page
        elif action == 'vod_manifest':
            result = get_vod_manifest(user, id)  # user contains token, id contains content_id
        elif action == 'vod_cdm':
            result = do_cdm(user, password, id)  # user contains pssh, password contains license_url, id contains token
        else:
            log_error(f"Unknown action: {action}")
            sys.exit(1)
        
        if result:
            print(json.dumps(result, indent=2))
        else:
            log_error("Action failed")
            sys.exit(1)
            
    except Exception as e:
        log_error(f"Execution failed: {str(e)}")
        sys.exit(1)

if __name__ == "__main__":
    main()

Required Actions

Your service script must implement these actions:

login

Authenticate user and return authentication tokens

refresh

Refresh authentication tokens when they expire

channels

Return list of available live channels

events

Return list of available events

manifest

Get stream manifest URL for a channel

cdm

Get DRM decryption keys for protected content

vod_categories

Get VOD content categories

vod_content

Get VOD content for a category

vod_manifest

Get VOD manifest URL

vod_cdm

Get DRM decryption keys for VOD content

DRM Support

For DRM-protected content, include Widevine device files (.wvd) in your script directory. The script should extract PSSH data from manifests and request decryption keys from license servers.

Testing Your Script

Test your script using the command line:

python3 YourService.py --action login --user "username" --password "password"
python3 YourService.py --action channels --user "token"
python3 YourService.py --action manifest --user "token" --id "channel_id"

Setting Up Telegram Notifications

xOTT Panel includes a comprehensive Telegram notification system for real-time alerts about events, service status updates, proxy assignments, and system monitoring.

Step 1: Create a Telegram Bot

  1. Open Telegram and search for @BotFather
  2. Send /newbot command
  3. Follow the instructions to create your bot
  4. Save the bot token provided by BotFather

Step 2: Get Your Chat ID

  1. Send a message to your bot
  2. Visit: https://api.telegram.org/bot{YOUR_BOT_TOKEN}/getUpdates
  3. Find your chat ID in the response

Step 3: Configure in xOTT Panel

  1. Log in to your xOTT Panel dashboard
  2. Navigate to SettingsNotifications
  3. Enable Telegram Notifications
  4. Enter your Bot Token
  5. Enter your Chat ID
  6. Configure notification types:
    • Stream Alerts
    • Event Notifications
    • Service Status Updates
    • Proxy Assignment Updates
    • System Monitoring Alerts
  7. Click Save Settings

Notification Types

Event Notifications

Real-time alerts when events start, stop, or restart

Service Notifications

Updates about service status changes and proxy assignments

Stream Alerts

Notifications about stream status and performance

System Monitoring

Alerts about system health and resource usage

Testing Notifications

After configuration, test your notifications by:

  1. Starting a test stream
  2. Scheduling a test event
  3. Changing service proxy assignments
Security Note: Keep your bot token and chat ID secure. Anyone with these credentials can send messages to your chat.

XUI Panel Integration Setup

xOTT Panel can export active streams, saved linear channels, and stable event-slot outputs into XUI-compatible panels. For production event delivery, configure stable service event slots first so external panel URLs remain fixed while xOTT remaps the live event behind each slot.

Step 1: Prepare XUI Panel

  1. Ensure your XUI Panel is running and accessible
  2. Note your XUI Panel URL (e.g., https://your-xui-panel.com)
  3. Generate an access code from the Access Codes page in XUI Panel (Admin API access code)
  4. Generate an API key from the Profile Settings page in XUI Panel

Step 2: Configure XUI Connection

  1. Log in to your xOTT Panel dashboard
  2. Navigate to XUI Integration in the sidebar
  3. Click Add Connection
  4. Fill in the connection details:
    • Connection Name: Descriptive name for this connection
    • XUI Panel URL: Your XUI Panel base URL
    • Access Code: Access code generated from XUI Access Codes page (Admin API access code)
    • API Key: API key generated from XUI Profile Settings page
    • Auto-Sync: Enable automatic synchronization
  5. Click Test Connection to verify
  6. Click Save Connection

Step 3: Export Streams or Stable Slots

  1. Navigate to Streams, Library, or Events in xOTT Panel
  2. Select active streams, saved channels, or configured stable event slots
  3. Click Export to XUI
  4. Choose your XUI connection
  5. Configure export settings:
    • Stream name and description
    • Category assignment
    • Quality settings
    • Auto-activation
  6. Click Export

Step 4: Manage Event Outputs

  1. Open Services and configure event slots from Service Outputs
  2. Use Auto slots for daily rotation, or dynamic rules for teams, competitions, categories, keywords, or regex matches
  3. Export the stable slot URLs to XUI instead of replacing event URLs every day
  4. Configure event settings:
    • Slot name and display title
    • Category assignment
    • Protected HLS source URL
    • Auto-sync behavior
  5. Click Export

Sync Status Monitoring

Monitor your XUI integration status through:

  • Sync Logs: View detailed sync operation logs
  • Status Dashboard: Real-time sync status overview
  • Error Handling: Automatic retry and error reporting
  • Notifications: Telegram alerts for sync events
Tip: Use stable event slots for sports services. XUI keeps the same input URL while xOTT updates the live event assignment, title, and protected playback path.