diff --git a/apps_script/Code.gs b/apps_script/Code.gs index 8c2acec..51eab3e 100644 --- a/apps_script/Code.gs +++ b/apps_script/Code.gs @@ -13,34 +13,323 @@ * 4. Type: Web app | Execute as: Me | Who has access: Anyone * 5. Copy the Deployment ID into config.json as "script_id" * - * CHANGE THE AUTH KEY BELOW TO YOUR OWN SECRET! + * TELEGRAM NOTIFICATIONS (optional): + * 1. Create a bot via @BotFather and copy the token into TELEGRAM_BOT_TOKEN + * 2. Get your chat ID and copy it into TELEGRAM_CHAT_ID + * 3. Set INSTANCE_NAME to identify this deployment in notifications + * 4. To enable periodic usage checks: Triggers → Add trigger → + * Function: checkUsageAndNotify | Time-driven | Minutes timer | + * Every N minutes (set to match USAGE_CHECK_INTERVAL_MINUTES) + * + * CHANGE ALL PLACEHOLDER VALUES BELOW BEFORE DEPLOYING! */ const AUTH_KEY = "CHANGE_ME_TO_A_STRONG_SECRET"; +const INSTANCE_NAME = "MyRelay"; + +const TELEGRAM_BOT_TOKEN = "YOUR_BOT_TOKEN_HERE"; +const TELEGRAM_CHAT_ID = "YOUR_CHAT_ID_HERE"; +const USAGE_CHECK_INTERVAL_MINUTES = 5; +const DAILY_EXECUTION_LIMIT = 20000; +const WARNING_THRESHOLDS = [0.5, 0.75, 0.9, 0.95, 0.99]; -// Keep browser capability headers (sec-ch-ua*, sec-fetch-*) intact. -// Some modern apps, notably Google Meet, use them for browser gating. const SKIP_HEADERS = { host: 1, connection: 1, "content-length": 1, "transfer-encoding": 1, "proxy-connection": 1, "proxy-authorization": 1, "priority": 1, te: 1, }; +const USAGE_STORAGE_KEY = "daily_usage_data"; + +function getUsageStore() { + const props = PropertiesService.getScriptProperties(); + let usage = props.getProperty(USAGE_STORAGE_KEY); + const now = new Date(); + const today = now.toDateString(); + + if (!usage) { + usage = { + date: today, + totalRequests: 0, + batchRequests: 0, + singleRequests: 0, + lastUpdated: now.toISOString(), + warningSent: [], + }; + props.setProperty(USAGE_STORAGE_KEY, JSON.stringify(usage)); + sendTelegramMessage( + "🚀 Domain Front Relay initialized\n" + + `Daily limit: ${DAILY_EXECUTION_LIMIT.toLocaleString()} requests\n` + + `Monitoring started for ${today}` + ); + } else { + usage = JSON.parse(usage); + if (usage.date !== today) { + const previousDayTotal = usage.totalRequests; + sendTelegramMessage( + `📊 Daily Reset - Previous Day Summary\n\n` + + `📅 Date: ${usage.date}\n` + + `✅ Total Requests: ${previousDayTotal.toLocaleString()}\n` + + `📈 Daily limit usage: ${((previousDayTotal / DAILY_EXECUTION_LIMIT) * 100).toFixed(1)}%\n` + + `🔄 Resetting counter for ${today}` + ); + usage = { + date: today, + totalRequests: 0, + batchRequests: 0, + singleRequests: 0, + lastUpdated: now.toISOString(), + warningSent: [], + }; + props.setProperty(USAGE_STORAGE_KEY, JSON.stringify(usage)); + } + } + return usage; +} + +function updateUsage(type, count) { + count = count || 1; + const usage = getUsageStore(); + usage.totalRequests += count; + + if (type === "batch") { + usage.batchRequests += count; + } else if (type === "single") { + usage.singleRequests += count; + } + + usage.lastUpdated = new Date().toISOString(); + PropertiesService.getScriptProperties().setProperty(USAGE_STORAGE_KEY, JSON.stringify(usage)); + + if (usage.totalRequests > DAILY_EXECUTION_LIMIT) { + sendTelegramMessage( + `🚨 CRITICAL: Daily execution limit EXCEEDED!\n\n` + + `📊 Current: ${usage.totalRequests.toLocaleString()}\n` + + `⚠️ Limit: ${DAILY_EXECUTION_LIMIT.toLocaleString()}\n` + + `🔴 Exceeded by: ${(usage.totalRequests - DAILY_EXECUTION_LIMIT).toLocaleString()}\n\n` + + `❗ Immediate action required!` + ); + return usage; + } + + const percentage = usage.totalRequests / DAILY_EXECUTION_LIMIT; + for (var i = 0; i < WARNING_THRESHOLDS.length; i++) { + const threshold = WARNING_THRESHOLDS[i]; + if (percentage >= threshold && usage.warningSent.indexOf(threshold) === -1) { + usage.warningSent.push(threshold); + PropertiesService.getScriptProperties().setProperty(USAGE_STORAGE_KEY, JSON.stringify(usage)); + + var emoji = "⚠️"; + var severity = "WARNING"; + if (threshold >= 0.95) { + emoji = "🚨"; + severity = "CRITICAL"; + } else if (threshold >= 0.9) { + emoji = "🔴"; + severity = "HIGH"; + } else if (threshold >= 0.75) { + emoji = "🟠"; + severity = "MEDIUM"; + } + + const remaining = DAILY_EXECUTION_LIMIT - usage.totalRequests; + const estimatedTime = estimateTimeRemaining(remaining); + + sendTelegramMessage( + `${emoji} ${severity} Usage Alert - ${Math.round(percentage * 100)}% of daily limit\n\n` + + `📊 Current usage: ${usage.totalRequests.toLocaleString()} / ${DAILY_EXECUTION_LIMIT.toLocaleString()}\n` + + `📈 Percentage: ${(percentage * 100).toFixed(1)}%\n` + + `✅ Remaining: ${remaining.toLocaleString()} requests\n` + + `⏱️ Estimated time remaining: ${estimatedTime}\n\n` + + `📋 Breakdown:\n` + + `• Single requests: ${usage.singleRequests.toLocaleString()}\n` + + `• Batch requests: ${usage.batchRequests.toLocaleString()}` + ); + } + } + + return usage; +} + +function estimateTimeRemaining(remainingRequests) { + const usage = getUsageStore(); + const now = new Date(); + const startOfDay = new Date(now); + startOfDay.setHours(0, 0, 0, 0); + + const hoursElapsed = (now - startOfDay) / (1000 * 60 * 60); + if (hoursElapsed < 0.1 || usage.totalRequests === 0) { + return "Unknown (insufficient data)"; + } + + const requestsPerHour = usage.totalRequests / hoursElapsed; + if (requestsPerHour === 0) return "Unknown"; + + const hoursRemaining = remainingRequests / requestsPerHour; + if (hoursRemaining < 1) { + return `${Math.round(hoursRemaining * 60)} minutes`; + } + return `${hoursRemaining.toFixed(1)} hours`; +} + +function sendTelegramMessage(message) { + if (TELEGRAM_BOT_TOKEN === "YOUR_BOT_TOKEN_HERE") { + console.log("Telegram not configured — skipping notification"); + return false; + } + + const labeledMessage = `🏷️ [${INSTANCE_NAME}]\n${message}`; + const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`; + const payload = { + chat_id: TELEGRAM_CHAT_ID, + text: labeledMessage, + parse_mode: "HTML", + }; + + try { + const options = { + method: "post", + contentType: "application/json", + payload: JSON.stringify(payload), + muteHttpExceptions: true, + }; + const response = UrlFetchApp.fetch(url, options); + const result = JSON.parse(response.getContentText()); + return result.ok; + } catch (err) { + console.error("Failed to send Telegram message:", err); + return false; + } +} + +function sendPeriodicUpdate() { + const usage = getUsageStore(); + const now = new Date(); + const percentage = (usage.totalRequests / DAILY_EXECUTION_LIMIT) * 100; + const remaining = DAILY_EXECUTION_LIMIT - usage.totalRequests; + + var statusEmoji = "✅"; + if (percentage >= 90) statusEmoji = "🚨"; + else if (percentage >= 75) statusEmoji = "🔴"; + else if (percentage >= 50) statusEmoji = "🟠"; + else if (percentage >= 25) statusEmoji = "🟡"; + + const message = + `⏰ Time: ${now.toLocaleString()}\n` + + `📊 Usage: ${usage.totalRequests.toLocaleString()} / ${DAILY_EXECUTION_LIMIT.toLocaleString()} (${percentage.toFixed(1)}%)\n` + + `${statusEmoji} Remaining: ${remaining.toLocaleString()} requests\n` + + `⏱️ Last update: ${new Date(usage.lastUpdated).toLocaleTimeString()}`; + + if (percentage >= 90) { + sendTelegramMessage(`🚨 URGENT: Approaching daily limit!\n${message}`); + } else { + sendTelegramMessage(message); + } +} + +function sendDailyUsageReport() { + const usage = getUsageStore(); + const percentage = (usage.totalRequests / DAILY_EXECUTION_LIMIT) * 100; + const remaining = DAILY_EXECUTION_LIMIT - usage.totalRequests; + + var statusIcon = "✅"; + var statusText = "Within limit"; + if (usage.totalRequests > DAILY_EXECUTION_LIMIT) { + statusIcon = "❌"; + statusText = "LIMIT EXCEEDED"; + } else if (percentage >= 90) { + statusIcon = "⚠️"; + statusText = "Critical - Near limit"; + } else if (percentage >= 75) { + statusIcon = "⚠️"; + statusText = "Warning - High usage"; + } + + const message = + `📊 Domain Front Relay - Daily Summary\n\n` + + `📅 Date: ${usage.date}\n` + + `🕐 Last update: ${new Date(usage.lastUpdated).toLocaleString()}\n\n` + + `Statistics:\n` + + `• Total Requests: ${usage.totalRequests.toLocaleString()}\n` + + `• Daily Limit: ${DAILY_EXECUTION_LIMIT.toLocaleString()}\n` + + `• Usage: ${percentage.toFixed(1)}%\n` + + `• Remaining: ${remaining.toLocaleString()}\n\n` + + `Breakdown:\n` + + `• Single Mode: ${usage.singleRequests.toLocaleString()}\n` + + `• Batch Mode: ${usage.batchRequests.toLocaleString()}\n\n` + + `${statusIcon} Status: ${statusText}`; + + return sendTelegramMessage(message); +} + function doPost(e) { try { var req = JSON.parse(e.postData.contents); if (req.k !== AUTH_KEY) return _json({ e: "unauthorized" }); - // Batch mode: { k, q: [...] } - if (Array.isArray(req.q)) return _doBatch(req.q); + if (Array.isArray(req.q)) { + updateUsage("batch", req.q.length); + return _doBatch(req.q); + } - // Single mode + updateUsage("single"); return _doSingle(req); } catch (err) { + sendTelegramMessage(`❌ Error in doPost\n\n${String(err)}`); return _json({ e: String(err) }); } } +function checkUsageAndNotify() { + const usage = getUsageStore(); + const now = new Date(); + const hour = now.getHours(); + const minute = now.getMinutes(); + + if (hour === 23 && minute >= 55) { + sendDailyUsageReport(); + } else { + sendPeriodicUpdate(); + } +} + +function resetDailyUsage() { + const props = PropertiesService.getScriptProperties(); + const now = new Date(); + const resetUsage = { + date: now.toDateString(), + totalRequests: 0, + batchRequests: 0, + singleRequests: 0, + lastUpdated: now.toISOString(), + warningSent: [], + }; + props.setProperty(USAGE_STORAGE_KEY, JSON.stringify(resetUsage)); + sendTelegramMessage( + `🔄 Daily usage manually reset\n` + + `New tracking started for ${now.toDateString()}\n` + + `Daily limit: ${DAILY_EXECUTION_LIMIT.toLocaleString()}` + ); +} + +function getCurrentUsageStats() { + const usage = getUsageStore(); + const percentage = (usage.totalRequests / DAILY_EXECUTION_LIMIT) * 100; + const remaining = DAILY_EXECUTION_LIMIT - usage.totalRequests; + + const stats = + `📈 Current Usage Stats\n\n` + + `📅 ${usage.date}\n` + + `✅ Total: ${usage.totalRequests.toLocaleString()} / ${DAILY_EXECUTION_LIMIT.toLocaleString()}\n` + + `📊 ${percentage.toFixed(1)}% used\n` + + `💚 Remaining: ${remaining.toLocaleString()}\n` + + `🔄 Single: ${usage.singleRequests.toLocaleString()}\n` + + `📦 Batch: ${usage.batchRequests.toLocaleString()}`; + + sendTelegramMessage(stats); + return usage; +} + function _doSingle(req) { if (!req.u || typeof req.u !== "string" || !req.u.match(/^https?:\/\//i)) { return _json({ e: "bad url" }); @@ -138,4 +427,4 @@ function _json(obj) { return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType( ContentService.MimeType.JSON ); -} +} \ No newline at end of file