V2 Release

This commit is contained in:
Ujwal223
2026-05-25 22:12:38 +05:45
parent 2d33dcb889
commit 842dc70829
38 changed files with 642 additions and 334 deletions
+3 -5
View File
@@ -1,6 +1,6 @@
/**
* FocusGram DOM Ad Blocker
* Removes sponsored posts, "Suggested for you" injections, and ad elements.
* SHould have Removed sponsored posts, "Suggested for you" injections, and ad elements.
* Uses structure-based selectors — NOT class names (those change weekly).
* Injected at DOCUMENT_END.
*/
@@ -93,8 +93,6 @@
if (hasAdditions) scanAndRemove();
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
const feed = document.querySelector('main') ?? document.body;
observer.observe(feed, { childList: true, subtree: true });
})();
+1 -1
View File
@@ -209,7 +209,7 @@
let response = await _fetch(input, init);
// Only intercept GraphQL feed queries
if (!url.includes('/graphql/query') && !url.includes('/api/v1/feed')) {
if (!url.includes('/graphql') && !url.includes('/api/v1/feed')) {
return response;
}
-83
View File
@@ -1,83 +0,0 @@
/**
* FocusGram Autoplay Blocker
* Injected at DOCUMENT_START — before Instagram's JS loads.
* Prevents video autoplay by:
* 1. Blocking play() calls on video elements
* 2. Disabling autoplay attribute
* 3. Removing preload attributes
*/
(function () {
'use strict';
window.__fgBlockAutoplay = false;
// Override HTMLMediaElement.play() to check our flag
const _play = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function () {
if (window.__fgBlockAutoplay) {
// Return a resolved promise to avoid breaking Instagram's code
return Promise.resolve();
}
return _play.call(this);
};
// Override autoplay property setter
const _videoDescriptor = Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'autoplay') || {};
const _originalAutoplaySetter = _videoDescriptor.set;
Object.defineProperty(HTMLVideoElement.prototype, 'autoplay', {
set: function (value) {
if (window.__fgBlockAutoplay && value) {
// Silently ignore autoplay attempts when blocking is enabled
return;
}
if (_originalAutoplaySetter) {
_originalAutoplaySetter.call(this, value);
}
},
get: function () {
if (_videoDescriptor.get) {
return _videoDescriptor.get.call(this);
}
return this.getAttribute('autoplay') !== null;
},
enumerable: _videoDescriptor.enumerable,
configurable: true,
});
// On page load and SPA navigation, scan for video elements and remove autoplay
const removeAutoplayFromVideos = () => {
document.querySelectorAll('video, [role="video"]').forEach(el => {
if (window.__fgBlockAutoplay) {
el.autoplay = false;
el.removeAttribute('autoplay');
if (el.paused === false) {
el.pause();
}
}
});
};
// Run on load and when document changes
removeAutoplayFromVideos();
if (!window.__fgAutoplayObserver) {
let _timer = null;
window.__fgAutoplayObserver = new MutationObserver(() => {
clearTimeout(_timer);
_timer = setTimeout(removeAutoplayFromVideos, 500);
});
window.__fgAutoplayObserver.observe(document.documentElement, {
childList: true,
subtree: true,
});
}
// Allow Flutter to toggle
window.__fgSetBlockAutoplay = function (enabled) {
window.__fgBlockAutoplay = !!enabled;
if (enabled) {
removeAutoplayFromVideos();
}
};
})();
+18 -11
View File
@@ -23,16 +23,22 @@
// Helper: Check if a node is an ad
const isAdNode = (node) => {
if (!node || typeof node !== 'object') return false;
return !!(
node.is_ad ||
node.ad_action_link ||
node.ad_id ||
(node.product_type && node.product_type === 'ad') ||
(node.ad_header_style && node.ad_header_style !== 'none') ||
(node.__typename && node.__typename === 'GraphAdStory')
);
if (!node || typeof node !== 'object') return false;
return !!(
node.is_ad ||
node.ad_id ||
node.ad_action_link ||
node.ad_action_links?.length > 0 ||
node.is_paid_partnership ||
node.sponsor_tags?.length > 0 ||
(node.commerciality_status === 'ad') ||
(node.commerciality_status === 'shoppable_feed_ad') ||
(node.product_type === 'ad') ||
(node.ad_header_style && node.ad_header_style !== 'none') ||
node.__typename === 'GraphAdStory' ||
node.__typename === 'XDTAdFeedUnit' ||
(node.__typename?.toLowerCase().includes('ad'))
);
};
// Helper: Check if a node is sponsored
@@ -158,6 +164,7 @@
}
}
};
// Override fetch
const _fetch = window.fetch.bind(window);
@@ -173,7 +180,7 @@
let response = await _fetch(input, init);
// Only intercept GraphQL feed queries
if (!url.includes('/graphql/query') && !url.includes('/api/v1/feed')) {
if (!url.includes('/graphql') && !url.includes('/api/v1/feed')) {
return response;
}
+27 -1
View File
@@ -78,7 +78,6 @@ class InstagramWebViewState extends State<InstagramWebView> {
// ── ContentBlockers — merged base + EasyList rules ──────────────
contentBlockers: WebViewConfig.baseContentBlockers,
// TODO Phase 1.5: merge EasyListParser.load() here at startup
// ── User Scripts — AT_DOCUMENT_START critical for ghost mode ─────
initialUserScripts: UnmodifiableListView(
@@ -96,6 +95,33 @@ class InstagramWebViewState extends State<InstagramWebView> {
onWebViewCreated: (controller) async {
_controller = controller;
//Interceptor for adblock
shouldInterceptRequest:
(controller, request) async {
final url = request.url.toString();
const adDomains = [
'an.facebook.com',
'connect.facebook.net',
'pixel.facebook.com',
'graph.facebook.com/logging',
'www.instagram.com/ajax/bz',
'www.instagram.com/api/v1/web/comet/logcalls',
'doubleclick.net',
'googletagmanager.com',
'scorecardresearch.com',
];
if (adDomains.any(url.contains)) {
return WebResourceResponse(
contentType: 'application/json',
httpStatus: WebResourceResponseHTTPStatus(statusCode: 200),
data: Uint8List.fromList(utf8.encode('{}')),
);
}
return null;
};
// Initialize GhostModeService
_ghostMode = GhostModeService();
await _ghostMode!.load();