from __future__ import annotations from flask import Blueprint, jsonify, request fallback_ux_bp = Blueprint("fallback_ux", __name__, url_prefix="/api/ux") fallback_updates_bp = Blueprint("fallback_updates", __name__, url_prefix="/api/updates") fallback_audio_quality_bp = Blueprint( "fallback_audio_quality", __name__, url_prefix="/api/audio-quality" ) fallback_recap_bp = Blueprint("fallback_recap", __name__, url_prefix="/api/recap") fallback_settings_bp = Blueprint( "fallback_settings", __name__, url_prefix="/api/settings" ) DEFAULT_AUDIO_SETTINGS = { "streaming_quality": "high", "adaptive_quality": True, "network_aware_quality": True, "device_specific_quality": True, "download_format": "flac", "download_sample_rate": "44.1kHz", "download_bit_depth": "16bit", "enable_loudness_normalization": True, "target_loudness": -14.0, "enable_adaptive_eq": False, "enable_spatial_audio_processing": False, "spatial_audio_format": "stereo", "enable_crossfade": False, "crossfade_duration": 2.0, "enable_gapless_playback": True, "enable_replaygain": True, } DEFAULT_UPDATE_SETTINGS = { "enableArtistMonitoring": False, "autoDownloadFavorites": False, "enableNotifications": False, "checkFrequency": "daily", "qualityPreference": "flac", "excludeExplicit": False, } DEFAULT_UD_SETTINGS = { "defaultQuality": "high", "autoAddToLibrary": True, "maxConcurrentDownloads": 3, } def _disabled_payload(feature: str, **payload): return {"enabled": False, "feature": feature, **payload} @fallback_ux_bp.get("/search/suggestions") def fallback_ux_search_suggestions(): query = request.args.get("q", "") context = request.args.get("context", "general") return jsonify( _disabled_payload( "advanced_ux", suggestions=[], query=query, context=context, total_count=0, ) ) @fallback_ux_bp.get("/discovery/recommendations") def fallback_ux_discovery(): recommendation_type = request.args.get("type", "mixed") return jsonify( _disabled_payload( "advanced_ux", recommendations=[], type=recommendation_type, total_count=0, ) ) @fallback_ux_bp.get("/contextual/suggestions") def fallback_ux_contextual(): track_id = request.args.get("track_id") context_type = request.args.get("context_type", "similar") return jsonify( _disabled_payload( "advanced_ux", suggestions=[], track_id=track_id, context_type=context_type, total_count=0, ) ) @fallback_ux_bp.get("/download/suggestions") def fallback_ux_download_suggestions(): query = request.args.get("q", "") return jsonify( _disabled_payload( "advanced_ux", suggestions=[], query=query, total_count=0, ) ) @fallback_ux_bp.get("/search/filters") def fallback_ux_filters(): return jsonify(_disabled_payload("advanced_ux", filters=[], total_count=0)) @fallback_ux_bp.post("/behavior/track") def fallback_ux_track_behavior(): return jsonify( _disabled_payload("advanced_ux", message="Behavior tracking skipped") ) @fallback_ux_bp.get("/behavior/profile") def fallback_ux_behavior_profile(): profile = { "user_id": None, "favorite_genres": [], "favorite_artists": [], "listening_patterns": {}, "download_preferences": {}, "interaction_patterns": {}, "last_updated": None, "search_history_count": 0, "recent_searches": [], } return jsonify(_disabled_payload("advanced_ux", profile=profile)) @fallback_ux_bp.get("/trending/content") def fallback_ux_trending(): return jsonify( _disabled_payload( "advanced_ux", trending=[], type=request.args.get("type", "mixed"), timeframe=request.args.get("timeframe", "week"), total_count=0, ) ) @fallback_ux_bp.post("/search/advanced") def fallback_ux_advanced_search(): payload = request.get_json(silent=True) or {} return jsonify( _disabled_payload( "advanced_ux", query=payload.get("query", ""), results={ "tracks": [], "albums": [], "artists": [], "playlists": [], }, ) ) @fallback_ux_bp.get("/suggestions/quick") def fallback_ux_quick_suggestions(): return jsonify( _disabled_payload( "advanced_ux", suggestions=[], type=request.args.get("type", "search"), total_count=0, ) ) @fallback_ux_bp.get("/personalization/preferences") def fallback_ux_get_preferences(): return jsonify( _disabled_payload( "advanced_ux", preferences={"enable_personalization": False}, ) ) @fallback_ux_bp.put("/personalization/preferences") def fallback_ux_update_preferences(): payload = request.get_json(silent=True) or {} return jsonify( _disabled_payload( "advanced_ux", message="Preferences saved in fallback mode", preferences=payload, ) ) @fallback_updates_bp.get("/stats") def fallback_updates_stats(): stats = { "followedArtists": 0, "newReleases": 0, "pendingDownloads": 0, "unreadNotifications": 0, } return jsonify(_disabled_payload("update_tracking", stats=stats)) @fallback_updates_bp.get("/recent") def fallback_updates_recent(): limit = request.args.get("limit", 20, type=int) offset = request.args.get("offset", 0, type=int) return jsonify( _disabled_payload( "update_tracking", updates=[], limit=limit, offset=offset, total=0, ) ) @fallback_updates_bp.get("/followed-artists") def fallback_updates_followed_artists(): return jsonify( _disabled_payload( "update_tracking", artists=[], limit=50, offset=0, total=0, ) ) @fallback_updates_bp.get("/settings") def fallback_updates_get_settings(): return jsonify(_disabled_payload("update_tracking", **DEFAULT_UPDATE_SETTINGS)) @fallback_updates_bp.post("/settings") def fallback_updates_set_settings(): payload = request.get_json(silent=True) or {} merged = {**DEFAULT_UPDATE_SETTINGS, **payload} return jsonify( _disabled_payload( "update_tracking", message="Settings saved in fallback mode", settings=merged, ) ) @fallback_updates_bp.get("/search/artists") def fallback_updates_search_artists(): return jsonify( _disabled_payload( "update_tracking", artists=[], query=request.args.get("q", ""), ) ) @fallback_updates_bp.post("/follow-artist") def fallback_updates_follow_artist(): payload = request.get_json(silent=True) or {} return jsonify( _disabled_payload( "update_tracking", message="Artist follow stored in fallback mode", artist_id=payload.get("artist_id"), ) ) @fallback_updates_bp.post("/unfollow-artist") def fallback_updates_unfollow_artist(): payload = request.get_json(silent=True) or {} return jsonify( _disabled_payload( "update_tracking", message="Artist unfollow stored in fallback mode", artist_id=payload.get("artist_id"), ) ) @fallback_updates_bp.get("/artist//follow-status") def fallback_updates_follow_status(artist_id: str): return jsonify( _disabled_payload( "update_tracking", is_following=False, artist_id=artist_id, follow_level="followed", auto_download_new_releases=False, preferred_quality="flac", ) ) @fallback_updates_bp.post("/artist/") def fallback_updates_update_artist(artist_id: str): payload = request.get_json(silent=True) or {} return jsonify( _disabled_payload( "update_tracking", message="Artist preferences saved in fallback mode", artist_id=artist_id, settings=payload, ) ) @fallback_updates_bp.post("/auto-download/") def fallback_updates_auto_download(release_id: str): return jsonify( _disabled_payload( "update_tracking", message="Download queued in fallback mode", release_id=release_id, ) ) @fallback_updates_bp.post("/release//mark-read") def fallback_updates_mark_read(release_id: str): return jsonify( _disabled_payload( "update_tracking", message="Marked as read", release_id=release_id, ) ) @fallback_updates_bp.post("/notifications/mark-all-read") def fallback_updates_mark_all_read(): return jsonify( _disabled_payload( "update_tracking", message="All notifications marked as read", ) ) @fallback_updates_bp.get("/export/followed-artists") def fallback_updates_export_followed_artists(): return jsonify(_disabled_payload("update_tracking", followed_artists=[])) @fallback_audio_quality_bp.get("/settings") def fallback_audio_get_settings(): return jsonify(_disabled_payload("audio_quality", settings=DEFAULT_AUDIO_SETTINGS)) @fallback_audio_quality_bp.post("/settings") def fallback_audio_set_settings(): payload = request.get_json(silent=True) or {} merged = {**DEFAULT_AUDIO_SETTINGS, **payload} return jsonify( _disabled_payload( "audio_quality", message="Audio quality settings saved in fallback mode", settings=merged, ) ) @fallback_audio_quality_bp.get("/network/status") def fallback_audio_network_status(): return jsonify( _disabled_payload( "audio_quality", network_status={"speed": 0, "quality": "unknown"}, ) ) @fallback_audio_quality_bp.get("/device/info") def fallback_audio_device_info(): return jsonify( _disabled_payload( "audio_quality", device_info={"type": "unknown"}, ) ) @fallback_audio_quality_bp.post("/apply-preset") def fallback_audio_apply_preset(): payload = request.get_json(silent=True) or {} return jsonify( _disabled_payload( "audio_quality", message="Preset applied in fallback mode", preset_name=payload.get("preset_name"), settings=DEFAULT_AUDIO_SETTINGS, ) ) @fallback_recap_bp.get("/available-years") def fallback_recap_available_years(): return jsonify(_disabled_payload("recap", available_years=[], total_recaps=0)) @fallback_recap_bp.get("/summary/") def fallback_recap_summary(year: int): return jsonify(_disabled_payload("recap", recap=None, year=year)) @fallback_recap_bp.get("/details/") def fallback_recap_details(year: int): return jsonify(_disabled_payload("recap", recap=None, year=year)) @fallback_recap_bp.post("/generate/") def fallback_recap_generate(year: int): return jsonify( _disabled_payload( "recap", message="Recap generation is unavailable", year=year, ) ) @fallback_recap_bp.post("/video/") def fallback_recap_video(year: int): return jsonify( _disabled_payload( "recap", message="Recap video generation is unavailable", year=year, ) ) @fallback_recap_bp.post("/share/") def fallback_recap_share(year: int): return jsonify( _disabled_payload( "recap", message="Share links are unavailable", year=year, share_url=None, ) ) @fallback_recap_bp.get("/shared/") def fallback_recap_shared(token: str): return jsonify(_disabled_payload("recap", recap=None, token=token)) @fallback_recap_bp.get("/compare//") def fallback_recap_compare(year1: int, year2: int): return jsonify(_disabled_payload("recap", comparison=None, years=[year1, year2])) @fallback_settings_bp.get("/universal-downloader") def fallback_universal_downloader_get(): return jsonify( _disabled_payload( "universal_downloader_settings", success=True, settings=DEFAULT_UD_SETTINGS, ) ) @fallback_settings_bp.post("/universal-downloader") def fallback_universal_downloader_post(): payload = request.get_json(silent=True) or {} merged = {**DEFAULT_UD_SETTINGS, **payload} return jsonify( _disabled_payload( "universal_downloader_settings", success=True, settings=merged, message="Settings saved in fallback mode", ) ) def _has_route(app, route: str) -> bool: return any(rule.rule == route for rule in app.url_map.iter_rules()) def register_optional_feature_fallbacks(app): if not _has_route(app, "/api/ux/search/suggestions"): app.register_blueprint(fallback_ux_bp) if not _has_route(app, "/api/updates/stats"): app.register_blueprint(fallback_updates_bp) if not _has_route(app, "/api/audio-quality/settings"): app.register_blueprint(fallback_audio_quality_bp) if not _has_route(app, "/api/recap/available-years"): app.register_blueprint(fallback_recap_bp) if not _has_route(app, "/api/settings/universal-downloader"): app.register_blueprint(fallback_settings_bp)