mirror of
https://github.com/Ujwal223/FocusGram.git
synced 2026-06-01 03:21:35 +02:00
V2 Release
This commit is contained in:
@@ -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 });
|
||||
})();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user