import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import '../focus_settings.dart'; /// Flutter sets these flags after settings load to enable ghost modes. /// Must be called from onWebViewCreated or on settings change. const String kSetGhostFlagsJS = ''' (function(){ // Placeholder — Flutter replaces these with actual setting values: // window.__fgPartialGhost = true/false; // window.__fgFullDmGhost = true/false; // window.__fgStoryGhost = true/false; // window.__fgGhostReady = true; // signals scripts can proceed })(); '''; // ═══════════════════════════════════════════════════════════════ // PARTIAL GHOST MODE — existing behavior // Blocks seen API patterns, WebSocket chat gateways, and uses // first-click gate for api/graphql on /direct/* (inbox loads, then block). // ═══════════════════════════════════════════════════════════════ const String kPartialGhostJS = r''' (function() { if (window.__fgPartialGhostPatched) return; window.__fgPartialGhostPatched = true; // ── Seen API patterns ────────────────────────────────────── var SEEN = [/\/api\/v1\/media\/[\w-]+\/seen\//, /\/api\/v1\/stories\/reel\/seen\//, /\/api\/v1\/direct_v2\/threads\/[\w-]+\/seen\//, /\/api\/v1\/direct_v2\/visual_message\/[\w-]+\/seen\//, /\/api\/v1\/live\/[\w-]+\/comment\/seen\//]; function isSeen(u) { for(var i=0;i buildUserScripts(FocusSettings settings) { final startScripts = []; final endScripts = []; // Prepend flag values directly into the script so they survive page navigation. // (evaluateJavascript-set flags are destroyed when the JS context resets on load.) // DM Ghost uses the comprehensive Full DM approach (URL blocklist, GraphQL ops, SW killer, beacon, WS). // it should have worked, but sadly it didnt if (settings.ghostMode) { startScripts.add('window.__fgFullDmGhost=true;$kFullDmGhostJS'); } if (settings.noAutoplay) startScripts.add(noAutoplayJS); // AT_DOCUMENT_END if (settings.noStories) endScripts.add(hideStoryTrayJS); if (settings.noReels) endScripts.add(hideReelsJS); if (settings.noDMs) endScripts.add(hideDMsJS); final scripts = []; if (startScripts.isNotEmpty) { scripts.add( UserScript( source: startScripts.join('\n'), injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, forMainFrameOnly: false, ), ); } if (endScripts.isNotEmpty) { scripts.add( UserScript( source: endScripts.join('\n'), injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, forMainFrameOnly: true, ), ); } return scripts; } // ── Existing non-ghost helpers (unchanged) ─────────────────── const String noAutoplayJS = ''' document.addEventListener('play', function(e) { if (e.target.tagName === 'VIDEO') e.target.pause(); }, true); '''; const String hideStoryTrayJS = ''' (function(){var s=document.createElement('style');s.textContent='[data-pagelet="story_tray"]{display:none!important}';document.head.appendChild(s);})(); '''; const String hideReelsJS = ''' (function(){new MutationObserver(function(){document.querySelectorAll('a[href="/reels/"]').forEach(function(e){var p=e.closest('div');if(p)p.style.setProperty('display','none','important')});document.querySelectorAll('a[href="/explore/"]').forEach(function(e){var p=e.closest('div');if(p)p.style.setProperty('display','none','important')})}).observe(document.body,{childList:true,subtree:true});})(); '''; const String hideDMsJS = ''' (function(){var s=document.createElement('style');s.textContent='a[href="/direct/inbox/"]{display:none!important}';document.head.appendChild(s);})(); ''';