"""VOD catalog: optional single M3U account for library listings (env DISPATCHARR_VOD_CATALOG_ACCOUNT_ID). Plus per-user playback / catalog policies stored in User.custom_properties: custom_properties = { "vod_playback_mode": "strict" | "prefer" | "auto", "vod_playback_account_ids": [10, 11], "allowed_movie_category_ids": [3, 7, 12], # empty/missing = all "allowed_series_category_ids": [4, 9], # empty/missing = all } """ import os def get_vod_catalog_account_id(): raw = (os.environ.get("DISPATCHARR_VOD_CATALOG_ACCOUNT_ID") or "").strip() if raw.isdigit(): return int(raw) return None def get_vod_playback_allowed_account_ids(): """ Optional comma-separated M3U account IDs used only for VOD stream selection (failover). Example: DISPATCHARR_VOD_PLAYBACK_ACCOUNT_IDS=10,11,12 When unset, any active M3U account with a relation may be used (default). """ raw = (os.environ.get("DISPATCHARR_VOD_PLAYBACK_ACCOUNT_IDS") or "").strip() if not raw: return None out = [] for part in raw.split(","): part = part.strip() if part.isdigit(): out.append(int(part)) return out if out else None def filter_m3u_relation_queryset_for_playback(qs): """ Apply DISPATCHARR_VOD_PLAYBACK_ACCOUNT_IDS allowlist to an M3U relation queryset. Used by VOD proxy and Xtream stream redirects so failover matches playback policy. """ allowed = get_vod_playback_allowed_account_ids() if allowed is not None: return qs.filter(m3u_account_id__in=allowed, m3u_account__is_active=True) return qs.filter(m3u_account__is_active=True) def _coerce_int_list(raw): out = [] if not raw: return out if isinstance(raw, (list, tuple)): items = raw elif isinstance(raw, str): items = raw.split(",") else: return out for x in items: try: out.append(int(str(x).strip())) except (TypeError, ValueError): continue return out def get_user_vod_prefs(user): """Return a normalized dict of per-user VOD policies. Safe with None / anonymous user.""" if not user or not getattr(user, "is_authenticated", False): return { "playback_mode": "auto", "playback_account_ids": [], "allowed_movie_category_ids": None, "allowed_series_category_ids": None, } cp = getattr(user, "custom_properties", None) or {} accounts = _coerce_int_list(cp.get("vod_playback_account_ids")) mode = (cp.get("vod_playback_mode") or "").strip().lower() if mode not in ("strict", "prefer", "auto"): mode = "auto" movie_cats = _coerce_int_list(cp.get("allowed_movie_category_ids")) series_cats = _coerce_int_list(cp.get("allowed_series_category_ids")) return { "playback_mode": mode, "playback_account_ids": accounts, "allowed_movie_category_ids": movie_cats or None, "allowed_series_category_ids": series_cats or None, } def filter_relation_qs_for_user_playback(qs, user): """Restrict a relation queryset based on the user's playback prefs. Returns (queryset, prefer_account_id). - mode=strict: only the user's accounts (queryset further restricted). - mode=prefer: queryset unchanged, returns the first account as preference for tie-breaking. - mode=auto (or no accounts): no per-user filter; the global allowlist already applies upstream. """ prefs = get_user_vod_prefs(user) accounts = prefs.get("playback_account_ids") or [] mode = prefs.get("playback_mode") or "auto" if accounts and mode == "strict": qs = qs.filter(m3u_account_id__in=accounts) prefer = accounts[0] elif accounts and mode == "prefer": prefer = accounts[0] else: prefer = None return qs, prefer def get_user_allowed_movie_category_ids(user): return get_user_vod_prefs(user).get("allowed_movie_category_ids") def get_user_allowed_series_category_ids(user): return get_user_vod_prefs(user).get("allowed_series_category_ids") def filter_movie_qs_for_user(qs, user): """Restrict a Movie queryset to only categories the user is allowed to see.""" cats = get_user_allowed_movie_category_ids(user) if cats: return qs.filter(m3u_relations__category_id__in=cats).distinct() return qs def filter_series_qs_for_user(qs, user): """Restrict a Series queryset to only categories the user is allowed to see.""" cats = get_user_allowed_series_category_ids(user) if cats: return qs.filter(m3u_relations__category_id__in=cats).distinct() return qs def filter_m3u_movie_relation_qs_for_user(qs, user): cats = get_user_allowed_movie_category_ids(user) if cats: return qs.filter(category_id__in=cats) return qs def filter_m3u_series_relation_qs_for_user(qs, user): cats = get_user_allowed_series_category_ids(user) if cats: return qs.filter(category_id__in=cats) return qs