FoxiGram/TMessagesProj/src/main/res/raw/webview_ext.js
instant992 8e79f2ee9c FoxiGram: Telegram client with built-in Xray VLESS proxy
Based on Nekogram. Key additions:
- Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app)
- Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so
- Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI
- Auto-restore proxy on restart, rebind to active network (LTE/WiFi)
- Server credentials externalized to git-ignored XrayServers.java (+ template)
- libxray Go source included; compiled .so, keystore, google-services.json ignored
2026-06-08 16:41:07 +04:00

242 lines
No EOL
12 KiB
JavaScript

/*
* Telegram-Android browser extension
*
* # Gestures
* This script captures whether touch event is consumed by a website, to otherwise apply
* down or right gesture. Use `event.preventDefault()` at `touchstart` to prevent those gestures.
* It is recommended to do `event.preventDefault()` when dragging or swiping is expected to be
* handled by a website.
*
* You can also globally disable swipes for X and/or Y with <meta> tags:
* - <meta name="tg:swipes:x" content="none">
* - <meta name="tg:swipes:y" content="allow">
* Please, use these <meta> tags as the last resort, as it disables convenient back and close
* gestures, degrading user experience.
*
* Since some websites don't do that, the script also captures `style` and `class` changes to
* hierarchy of a touch element, and does equivalent of `preventDefault` if those changes happen
* while `touchstart` or `touchmove` events.
*
* === feature is hidden under debug button ===
* # Action Bar and Navigation Bar colors
* Top action bar and bottom navigation bar colors are defined with:
* - <meta name="tg:theme-accent" content="#FFFFFF" /> — action bar, usually an accent color
* - <meta name="theme-color" content="#FFFFFF" /> — action bar, usually an accent color
* - <meta name="tg:theme-background" content="#FFFFFF" /> — navigation bar
* - <meta name="theme-background-color" content="#FFFFFF" /> — navigation bar
* - <body> `background-color` css style — fallback
* `media` attribute on <meta> is also supported, feel free to use `prefers-color-scheme`
*/
if (!window.__tg__webview_set) {
window.__tg__webview_set = true;
(function () {
const DEBUG = $DEBUG$;
// Touch gestures hacks
const isImageViewer = () => {
if (!document.body.children || document.body.children.length != 1) return false;
const img = document.querySelector('body > img');
return img && img.tagName && img.tagName.toLowerCase() === 'img' && img.src === window.location.href;
}
const swipesDisabled = axis =>
(document.querySelector(`meta[name="tg:swipes:${axis}"]`)||{}).content === 'none';
let prevented = false;
let awaitingResponse = false;
let touchElement = null;
let mutatedWhileTouch = false;
let whiletouchstart = false, whiletouchmove = false;
document.addEventListener('touchstart', e => {
touchElement = e.target;
awaitingResponse = true;
whiletouchstart = true;
if (isImageViewer()) {
if (window.TelegramWebviewProxy) {
const allowScrollX = window.visualViewport && window.visualViewport.offsetLeft == 0 && !swipesDisabled('x');
const allowScrollY = window.visualViewport && window.visualViewport.offsetTop == 0 && !swipesDisabled('y');
if (DEBUG) {
console.log('tgbrowser allowScroll sent after "touchstart": x=' + allowScrollX + ' y=' + allowScrollY + ' inside image viewer');
}
window.TelegramWebviewProxy.postEvent('allowScroll', JSON.stringify([ allowScrollX, allowScrollY ]));
}
awaitingResponse = false;
}
}, true);
document.addEventListener('touchstart', e => {
whiletouchstart = false;
}, false);
document.addEventListener('touchmove', e => {
whiletouchstart = false;
whiletouchmove = true;
if (awaitingResponse) {
setTimeout(() => {
if (awaitingResponse) {
if (window.TelegramWebviewProxy) {
const allowScrollX = !prevented && (!window.visualViewport || window.visualViewport.offsetLeft == 0) && !mutatedWhileTouch && !swipesDisabled('x');
const allowScrollY = !prevented && (!window.visualViewport || window.visualViewport.offsetTop == 0) && !mutatedWhileTouch && !swipesDisabled('y');
if (DEBUG) {
console.log('tgbrowser allowScroll sent after "touchmove": x=' + allowScrollX + ' y=' + allowScrollY, { prevented, mutatedWhileTouch });
}
window.TelegramWebviewProxy.postEvent('allowScroll', JSON.stringify([ allowScrollX, allowScrollY ]));
}
prevented = false;
awaitingResponse = false;
}
mutatedWhileTouch = false;
}, 16);
}
}, true);
document.addEventListener('touchmove', e => {
whiletouchmove = false;
}, false);
document.addEventListener('scroll', e => {
if (!e.target) return;
const allowScrollX = e.target.scrollLeft == 0 && (!window.visualViewport || window.visualViewport.offsetLeft == 0) && !prevented && !mutatedWhileTouch && !swipesDisabled('x');
const allowScrollY = e.target.scrollTop == 0 && (!window.visualViewport || window.visualViewport.offsetTop == 0) && !prevented && !mutatedWhileTouch && !swipesDisabled('y');
if (DEBUG) {
console.log('tgbrowser scroll on' + e.target + ' scrollLeft=' + e.target.scrollLeft + ' scrollTop=' + e.target.scrollTop);
}
if (awaitingResponse) {
if (window.TelegramWebviewProxy) {
if (DEBUG) {
console.log('tgbrowser allowScroll sent after "scroll": x=' + allowScrollX + ' y=' + allowScrollY, { prevented, mutatedWhileTouch, scrollLeft: e.target.scrollLeft, scrollTop: e.target.scrollTop });
}
window.TelegramWebviewProxy.postEvent('allowScroll', JSON.stringify([allowScrollX, allowScrollY]));
}
awaitingResponse = false;
}
prevented = false;
mutatedWhileTouch = false;
}, true);
if (TouchEvent) {
const originalPreventDefault = TouchEvent.prototype.preventDefault;
TouchEvent.prototype.preventDefault = function () {
prevented = true;
originalPreventDefault.call(this);
};
const originalStopPropagation = TouchEvent.prototype.stopPropagation;
TouchEvent.prototype.stopPropagation = function () {
if (this.type === 'touchmove') {
whiletouchmove = false;
} else if (this.type === 'touchstart') {
whiletouchstart = false;
}
originalStopPropagation.call(this);
};
}
const isParentOf = (e, p) => {
if (!e || !p) return false;
if (e == p) return true;
return isParentOf(e.parentElement, p);
}
new MutationObserver(mutationList => {
const isTouchElement = touchElement && !![...(mutationList||[])]
.filter(r => r && (r.attributeName === 'style' || r.attributeName === 'class'))
.map(r => r.target)
.filter(e => !!e && e != document.body && e != document.documentElement)
.find(e => isParentOf(touchElement, e));
if (isTouchElement) { // && (whiletouchstart || whiletouchmove)) {
if (DEBUG) {
console.log('tgbrowser mutation detected', mutationList);
}
mutatedWhileTouch = true;
}
}).observe(document, { attributes: true, childList: true, subtree: true });
// Retrieving colors
const __tg__backgroundColor = () => {
try {
return window.getComputedStyle(document.body, null).getPropertyValue('background-color');
} catch (e) {
return null;
}
}
const __tg__metaColor = name =>
[...document.querySelectorAll(`meta[name="${name}"]`)]
.filter(meta => !meta.media || window.matchMedia && window.matchMedia(meta.media).matches)
.map(meta => meta.content)[0];
const __tg__cssColorToRGBA = color => {
if (!color) return null;
if (color[0] === '#') {
let hex = color.slice(1);
if (hex.length === 3 || hex.length === 4) {
hex = hex.split('').map(char => char + char).join('');
}
return [parseInt(hex.slice(0,2), 16), parseInt(hex.slice(2,4), 16), parseInt(hex.slice(4,6), 16), hex.length <= 6 ? 1 : parseInt(hex.slice(6,8), 16) / 255];
}
const colorMatch = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
if (colorMatch) {
return [parseInt(colorMatch[1]), parseInt(colorMatch[2]), parseInt(colorMatch[3]), colorMatch[4] ? parseFloat(colorMatch[4]) : 1];
}
return null;
};
let __tg__lastActionBarColor, __tg__lastNavigationBarColor;
window.__tg__postColorsChange = () => {
const actionBarColor = JSON.stringify(__tg__cssColorToRGBA(
__tg__metaColor("tg:theme-accent") ||
__tg__metaColor("theme-color") ||
__tg__backgroundColor()
));
const navigationBarColor = JSON.stringify(__tg__cssColorToRGBA(
__tg__metaColor("tg:theme-background") ||
__tg__metaColor("theme-background-color") ||
__tg__backgroundColor()
));
if (window.TelegramWebviewProxy) {
if (actionBarColor != __tg__lastActionBarColor) {
if (DEBUG) {
console.log('tgbrowser actionbar color', actionBarColor);
}
window.TelegramWebviewProxy.postEvent("actionBarColor", __tg__lastActionBarColor = actionBarColor);
}
if (navigationBarColor != __tg__lastNavigationBarColor) {
if (DEBUG) {
console.log('tgbrowser navbar color', navigationBarColor);
}
window.TelegramWebviewProxy.postEvent("navigationBarColor", __tg__lastNavigationBarColor = navigationBarColor);
}
}
};
const __tg__colorsObserver = new MutationObserver(() => {
window.__tg__postColorsChange();
setTimeout(window.__tg__postColorsChange, 500);
});
window.__tg__listenColors = () => {
[
document,
document.body,
...document.querySelectorAll('meta[name="tg:theme-accent"]'),
...document.querySelectorAll('meta[name="tg:theme-background"]'),
...document.querySelectorAll('meta[name="theme-color"]'),
...document.querySelectorAll('meta[name="theme-background-color"]')
].filter(e => !!e).map(e => __tg__colorsObserver.observe(e, { attributes: true }));
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', () => window.__tg__postColorsChange());
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => window.__tg__postColorsChange());
}
};
window.__tg__listenColors();
window.addEventListener('ready', __tg__listenColors, true);
window.__tg__postColorsChange();
})();
};
setTimeout(function () {
const site_name = (
(document.querySelector('meta[property="og:site_name"]') || {}).content ||
(document.querySelector('meta[property="og:title"]') || {}).content
);
if (window.TelegramWebviewProxy && window.TelegramWebviewProxy.postEvent) {
if (site_name) {
window.TelegramWebviewProxy.postEvent('siteName', site_name);
} else {
window.TelegramWebviewProxy.postEvent('siteNameEmpty');
}
}
if (window.__tg__listenColors) {
window.__tg__listenColors();
}
if (window.__tg__postColorsChange) {
window.__tg__postColorsChange();
}
}, 10);