Enhance Telegram bot functionality and improve error handling
- Introduced a new function to set the default menu button for the Telegram bot's Web App. - Updated the initData validation process to provide detailed error messages for authorization failures. - Refactored the validate_init_data function to return both username and reason for validation failure. - Enhanced the web application to handle access denial more gracefully, providing users with hints on how to access the calendar. - Improved the README with additional instructions for configuring the bot's menu button and Web App URL. - Updated tests to reflect changes in the validation process and error handling.
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
(function () {
|
||||
const FETCH_TIMEOUT_MS = 15000;
|
||||
const RETRY_DELAY_MS = 800;
|
||||
const RETRY_AFTER_ACCESS_DENIED_MS = 1200;
|
||||
|
||||
const MONTHS = [
|
||||
"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
|
||||
"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"
|
||||
@@ -34,16 +38,33 @@
|
||||
return new Date(d.getFullYear(), d.getMonth(), diff);
|
||||
}
|
||||
|
||||
/** Get tgWebAppData value from hash when it contains unencoded & and = (URLSearchParams would split it). Value runs from tgWebAppData= until next &tgWebApp or end. */
|
||||
function getTgWebAppDataFromHash(hash) {
|
||||
var idx = hash.indexOf("tgWebAppData=");
|
||||
if (idx === -1) return "";
|
||||
var start = idx + "tgWebAppData=".length;
|
||||
var end = hash.indexOf("&tgWebApp", start);
|
||||
if (end === -1) end = hash.length;
|
||||
var raw = hash.substring(start, end);
|
||||
try {
|
||||
return decodeURIComponent(raw);
|
||||
} catch (e) {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
function getInitData() {
|
||||
var fromSdk = (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.initData) || "";
|
||||
if (fromSdk) return fromSdk;
|
||||
var hash = window.location.hash ? window.location.hash.slice(1) : "";
|
||||
if (hash.indexOf("tgWebAppData=") === 0) {
|
||||
if (hash) {
|
||||
var fromHash = getTgWebAppDataFromHash(hash);
|
||||
if (fromHash) return fromHash;
|
||||
try {
|
||||
return decodeURIComponent(hash.substring("tgWebAppData=".length));
|
||||
} catch (e) {
|
||||
return hash.substring("tgWebAppData=".length);
|
||||
}
|
||||
var hashParams = new URLSearchParams(hash);
|
||||
var tgFromHash = hashParams.get("tgWebAppData");
|
||||
if (tgFromHash) return decodeURIComponent(tgFromHash);
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
var q = window.location.search ? new URLSearchParams(window.location.search).get("tgWebAppData") : null;
|
||||
if (q) {
|
||||
@@ -59,9 +80,9 @@
|
||||
function getInitDataDebug() {
|
||||
var sdk = !!(window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.initData);
|
||||
var hash = window.location.hash ? window.location.hash.slice(1) : "";
|
||||
var hashHasData = hash.indexOf("tgWebAppData=") === 0;
|
||||
var hashHasData = !!(hash && (getTgWebAppDataFromHash(hash) || new URLSearchParams(hash).get("tgWebAppData")));
|
||||
var queryHasData = !!(window.location.search && new URLSearchParams(window.location.search).get("tgWebAppData"));
|
||||
return "SDK: " + (sdk ? "да" : "нет") + ", hash: " + (hashHasData ? hash.length + " симв." : "нет") + ", query: " + (queryHasData ? "да" : "нет");
|
||||
return "SDK: " + (sdk ? "да" : "нет") + ", hash: " + (hashHasData ? hash.length + " симв." : (hash ? "есть, без tgWebAppData" : "нет")) + ", query: " + (queryHasData ? "да" : "нет");
|
||||
}
|
||||
|
||||
function isLocalhost() {
|
||||
@@ -69,7 +90,19 @@
|
||||
return h === "localhost" || h === "127.0.0.1" || h === "";
|
||||
}
|
||||
|
||||
function hasTelegramHashButNoInitData() {
|
||||
var hash = window.location.hash ? window.location.hash.slice(1) : "";
|
||||
if (!hash) return false;
|
||||
try {
|
||||
var keys = Array.from(new URLSearchParams(hash).keys());
|
||||
var hasVersion = keys.indexOf("tgWebAppVersion") !== -1;
|
||||
var hasData = keys.indexOf("tgWebAppData") !== -1 || getTgWebAppDataFromHash(hash);
|
||||
return hasVersion && !hasData;
|
||||
} catch (e) { return false; }
|
||||
}
|
||||
|
||||
function showAccessDenied() {
|
||||
var debugStr = getInitDataDebug();
|
||||
if (headerEl) headerEl.hidden = true;
|
||||
if (weekdaysEl) weekdaysEl.hidden = true;
|
||||
calendarEl.hidden = true;
|
||||
@@ -78,7 +111,13 @@
|
||||
errorEl.hidden = true;
|
||||
accessDeniedEl.hidden = false;
|
||||
var debugEl = document.getElementById("accessDeniedDebug");
|
||||
if (debugEl) debugEl.textContent = getInitDataDebug();
|
||||
if (debugEl) debugEl.textContent = debugStr;
|
||||
var hintEl = document.getElementById("accessDeniedHint");
|
||||
if (hintEl) {
|
||||
hintEl.textContent = hasTelegramHashButNoInitData()
|
||||
? "Откройте календарь через кнопку меню бота (⋮ или «Календарь»), а не через «Открыть в браузере» или прямую ссылку."
|
||||
: "Откройте календарь из Telegram.";
|
||||
}
|
||||
}
|
||||
|
||||
function hideAccessDenied() {
|
||||
@@ -96,21 +135,19 @@
|
||||
const headers = {};
|
||||
if (initData) headers["X-Telegram-Init-Data"] = initData;
|
||||
var controller = new AbortController();
|
||||
var timeoutId = setTimeout(function () { controller.abort(); }, 15000);
|
||||
var timeoutId = setTimeout(function () { controller.abort(); }, FETCH_TIMEOUT_MS);
|
||||
try {
|
||||
var res = await fetch(url, { headers: headers, signal: controller.signal });
|
||||
clearTimeout(timeoutId);
|
||||
if (res.status === 403) {
|
||||
throw new Error("ACCESS_DENIED");
|
||||
}
|
||||
if (res.status === 403) throw new Error("ACCESS_DENIED");
|
||||
if (!res.ok) throw new Error("Ошибка загрузки");
|
||||
return res.json();
|
||||
} catch (e) {
|
||||
clearTimeout(timeoutId);
|
||||
if (e.name === "AbortError") {
|
||||
throw new Error("Не удалось загрузить данные. Проверьте интернет.");
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +243,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** If allowed (initData or localhost), call onAllowed(); otherwise show access denied. When inside Telegram WebApp but initData is empty, retry once after a short delay (initData may be set asynchronously). */
|
||||
function requireTelegramOrLocalhost(onAllowed) {
|
||||
var initData = getInitData();
|
||||
var isLocal = isLocalhost();
|
||||
if (initData) { onAllowed(); return; }
|
||||
if (isLocal) { onAllowed(); return; }
|
||||
if (window.Telegram && window.Telegram.WebApp) {
|
||||
setTimeout(function () {
|
||||
initData = getInitData();
|
||||
if (initData) { onAllowed(); return; }
|
||||
showAccessDenied();
|
||||
loadingEl.classList.add("hidden");
|
||||
}, RETRY_DELAY_MS);
|
||||
return;
|
||||
}
|
||||
showAccessDenied();
|
||||
loadingEl.classList.add("hidden");
|
||||
}
|
||||
|
||||
function setNavEnabled(enabled) {
|
||||
if (prevBtn) prevBtn.disabled = !enabled;
|
||||
if (nextBtn) nextBtn.disabled = !enabled;
|
||||
@@ -229,7 +285,7 @@
|
||||
setNavEnabled(true);
|
||||
if (window.Telegram && window.Telegram.WebApp && !window._initDataRetried) {
|
||||
window._initDataRetried = true;
|
||||
setTimeout(loadMonth, 1200);
|
||||
setTimeout(loadMonth, RETRY_AFTER_ACCESS_DENIED_MS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -250,5 +306,9 @@
|
||||
loadMonth();
|
||||
});
|
||||
|
||||
runWhenReady(loadMonth);
|
||||
runWhenReady(function () {
|
||||
requireTelegramOrLocalhost(function () {
|
||||
loadMonth();
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user