Core components and modules of the Cricfy Kodi Plugin
The Cricfy Kodi Plugin is built with a modular architecture. Each module in the lib/ directory serves a specific purpose, making the codebase maintainable and extensible.
def get_providers(): """ Fetches and decrypts the list of providers from Cricfy. Uses caching to avoid repeated network calls. """ cached_providers = cache.get(PROVIDERS_CACHE_KEY) if cached_providers and isinstance(cached_providers, str): return json.loads(cached_providers) url = get_provider_api_url() response = fetch_url(f"{url}/cats.txt", timeout=15) decrypted_data = decrypt_data(response) providers = json.loads(decrypted_data) cache.set(PROVIDERS_CACHE_KEY, decrypted_data) return providers
Retrieves the list of available providers. Checks cache first, then fetches from the remote API if needed.get_channels(provider_url) from providers.py:62-90
def get_channels(provider_url: str): """ Fetches channels for a specific provider. """ channel_cache_key = f"channels_{_hash_key(provider_url)}" cached_channels = cache.get(channel_cache_key) # Check cache with TTL validation if cached_channels: channel_data = json.loads(cached_channels) fetch_time = float(channel_data.get('fetch_time')) if time.time() - fetch_time <= CHANNEL_CACHE_TTL: return [PlaylistItem.from_dict(item) for item in channels] # Fetch and parse M3U content = fetch_url(provider_url, timeout=15) content = decrypt_content(content) channels = parse_m3u(content) # Cache with timestamp cache.set(channel_cache_key, json.dumps({ 'channels': json.dumps(channels, default=lambda o: o.to_dict()), 'fetch_time': time.time() })) return channels
Fetches and parses channels from a provider’s M3U playlist URL. Implements time-based cache validation.
From m3u_parser.py:35-145, this function parses M3U content line-by-line:Supported directives:
#EXTINF - Extracts channel title and attributes (tvg-logo, group-title) from m3u_parser.py:53-66
#EXTVLCOPT - Extracts VLC options (http-user-agent, http-referrer) from m3u_parser.py:68-73
#EXTHTTP - Parses JSON headers (cookie, user-agent) from m3u_parser.py:75-85
#KODIPROP:inputstream.adaptive.license_key - Extracts DRM license strings from m3u_parser.py:87-89
URL lines - Stream URLs with optional pipe-separated parameters from m3u_parser.py:121-140
URL parameter parsing:
if "|" in full_url_line: url_parts = full_url_line.split("|") current_item.url = url_parts[0] params = url_parts[1].split("&") for p in params: if "=" in p: k, v = p.split("=", 1) if k.lower() == "user-agent": current_item.user_agent = v elif k.lower() == "referer": current_item.referer = v # ... more parameter handling
decrypt_data(encrypted_base64) from crypto_utils.py:42-63
def decrypt_data(encrypted_base64: str) -> Optional[str]: clean_base64 = encrypted_base64.strip().replace("\n", "").replace("\r", "") ciphertext = base64.b64decode(clean_base64) for key_info in keys().values(): result = try_decrypt(ciphertext, key_info) if result is not None: return result return None
Decrypts Base64-encoded provider data. Tries all available keys until successful.decrypt_content(content) from crypto_utils.py:88-128
def decrypt_content(content: str) -> str: # Check if already plain M3U if content.startswith("#EXTM3U") or content.startswith("#EXTINF"): return content # Extract encrypted parts part1 = trimmed_content[0:10] part2 = trimmed_content[34:-54] part3 = trimmed_content[-10:] encrypted_data_str = part1 + part2 + part3 iv_base64 = trimmed_content[10:34] key_base64 = trimmed_content[-54:-10] # Decrypt using extracted key and IV cipher = AES.new(key, AES.MODE_CBC, iv) decrypted_padded = cipher.decrypt(encrypted_bytes) decrypted_data = unpad(decrypted_padded, AES.block_size) return decrypted_data.decode('utf-8')
Decrypts M3U playlist content with embedded key/IV. Falls back to original content if decryption fails.try_decrypt(ciphertext, key_info) from crypto_utils.py:66-85Attempts decryption with a specific key and validates the result.