IQ Rewards backend ✅ - all fixed
// ==========================================
// 🧠 IQ REWARDS - FINAL BACKEND ENGINE (V3.0)
// ==========================================
const ROUND_DURATION = 45000; // 45 Seconds Total Round
const BETTING_WINDOW = 18000; // 🔥 BUG FIXED: 15 Seconds open for betting (Fixes the 32s Red Popup Bug)
// 📢 TELEGRAM CONFIGURATION
const TELEGRAM_TOKEN = "8837257790:AAEeCd7is1MLCa1UQyUrJjQ7hjNcL9HRY48";
const ADMIN_CHAT_ID = "966116673";
function doPost(e) {
try {
if (!e || !e.postData || !e.postData.contents) {
return ContentService.createTextOutput(JSON.stringify({success: false, message: "Invalid API Request"})).setMimeType(ContentService.MimeType.JSON);
}
var ss = SpreadsheetApp.getActiveSpreadsheet();
var req = JSON.parse(e.postData.contents);
if (req.callback_query) return handleTelegramAction(req.callback_query);
var action = req.action;
if (action === "register") return ContentService.createTextOutput(JSON.stringify(registerUser(ss, req))).setMimeType(ContentService.MimeType.JSON);
if (action === "login") return ContentService.createTextOutput(JSON.stringify(loginUser(ss, req))).setMimeType(ContentService.MimeType.JSON);
var auth = verifySession(ss, req.phone, req.token);
if (!auth.success) return ContentService.createTextOutput(JSON.stringify(auth)).setMimeType(ContentService.MimeType.JSON);
var res;
if (action === "getUserData") res = getUserData(ss, req.phone);
else if (action === "deposit") res = depositFunds(ss, req);
else if (action === "withdraw") res = withdrawFunds(ss, req);
else if (action === "getRound") res = getRound(ss, req.phone);
else if (action === "placeIqBet") res = placeIqBet(ss, req);
else if (action === "submitIqAnswer") res = submitIqAnswer(ss, req);
else if (action === "cheatDetected") res = cheatDetected(ss, req);
else if (action === "getRoundResult") res = getRoundResult(ss, req);
else res = { success: false, message: "Invalid API Action" };
return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService.createTextOutput(JSON.stringify({success: false, message: "Server Error: " + error.message})).setMimeType(ContentService.MimeType.JSON);
}
}
// 🔐 SESSION TOKEN AUTHENTICATION
function verifySession(ss, phone, token) {
var sheet = ss.getSheetByName("Users");
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
if (data[i][2].toString() === phone.toString()) {
if (data[i][10] === "Blocked") return { success: false, message: "Account Blocked due to cheating." };
if (data[i][12].toString() === token.toString()) {
return { success: true, row: i + 1, streak: data[i][6] || 0 };
}
return { success: false, message: "Session Expired! Logged in from another device." };
}
}
return { success: false, message: "User not found." };
}
// 🚫 REGISTRATION (With Admin Secret OTP)
function registerUser(ss, data) {
var otpSheet = ss.getSheetByName("Common OTP");
var validOtp = otpSheet.getRange("A2").getValue().toString().trim();
if (data.otp.toString().trim() !== validOtp) return { success: false, message: "Invalid Admin OTP!" };
var phoneStr = data.phone.toString();
if (/^(\d)\1{9}$/.test(phoneStr)) return { success: false, message: "Spam numbers not allowed!" };
if (phoneStr.length !== 10) return { success: false, message: "Invalid 10-digit number." };
var sheet = ss.getSheetByName("Users");
var users = sheet.getDataRange().getValues();
for (var i = 1; i < users.length; i++) {
if (users[i][2].toString() === phoneStr) return { success: false, message: "Mobile number already registered!" };
}
var newId = "IQ" + Math.floor(Math.random() * 90000 + 10000);
sheet.appendRow([newId, data.name, phoneStr, "", 0.00, 0.00, 0, 0, 0, new Date(), "Active", data.pass, "", data.refCode || ""]);
return { success: true, message: "Account Created!" };
}
// 🔑 LOGIN
function loginUser(ss, data) {
var sheet = ss.getSheetByName("Users");
var users = sheet.getDataRange().getValues();
for (var i = 1; i < users.length; i++) {
if (users[i][2].toString() === data.phone.toString() && users[i][11].toString() === data.pass) {
if (users[i][10] !== "Active") return { success: false, message: "Account is Blocked/Inactive." };
var newToken = Utilities.getUuid();
sheet.getRange(i + 1, 13).setValue(newToken);
sheet.getRange(i + 1, 10).setValue(new Date());
SpreadsheetApp.flush();
var playBal = parseFloat(users[i][4]) || 0;
var winBal = parseFloat(users[i][5]) || 0;
return { success: true, token: newToken, balance: (playBal + winBal).toFixed(2), playBal: playBal.toFixed(2), winBal: winBal.toFixed(2) };
}
}
return { success: false, message: "Invalid Mobile or Password!" };
}
// 👤 GET USER DATA
function getUserData(ss, phone) {
var uSheet = ss.getSheetByName("Users");
var uData = uSheet.getDataRange().getValues();
var totalBal = 0, playBal = 0, winBal = 0;
for (var i = 1; i < uData.length; i++) {
if (uData[i][2].toString() === phone.toString()) {
playBal = parseFloat(uData[i][4]) || 0;
winBal = parseFloat(uData[i][5]) || 0;
totalBal = playBal + winBal;
uSheet.getRange(i + 1, 10).setValue(new Date());
break;
}
}
var tSheet = ss.getSheetByName("Transactions");
var tData = tSheet.getDataRange().getValues();
var history = [];
for (var j = tData.length - 1; j >= 1; j--) {
if (tData[j][2].toString() === phone.toString()) {
history.push({ date: tData[j][1], type: tData[j][3], amount: parseFloat(tData[j][4]), status: tData[j][5] });
if (history.length >= 10) break;
}
}
return { success: true, balance: totalBal.toFixed(2), playBal: playBal.toFixed(2), winBal: winBal.toFixed(2), history: history };
}
// 🧠 MATH LOGIC: LEVEL 4 EXPERT ENGINE (70% ADMIN PROFIT TRAP)
function generateMathQuestion(amount, streak) {
var isTrue = Math.random() < 0.30;
var qStr = "", ans = "TRUE";
var baseLevel = 0;
if (amount <= 10) baseLevel = 0;
else if (amount <= 25) baseLevel = 1;
else if (amount <= 50) baseLevel = 2;
else baseLevel = 3;
var streakBonus = 0;
if (streak === 1) streakBonus = 1;
else if (streak >= 2) streakBonus = 2;
var effectiveLevel = baseLevel + streakBonus;
if (effectiveLevel > 4) effectiveLevel = 4;
if (effectiveLevel === 0) {
var n1 = Math.floor(Math.random() * 10) + 5;
var n2 = Math.floor(Math.random() * 10) + 1;
var sum = n1 + n2;
var displayedSum = isTrue ? sum : sum + [1, -1, 2][Math.floor(Math.random() * 3)];
qStr = n1 + " + " + n2 + " = " + displayedSum;
ans = isTrue ? "TRUE" : "FALSE";
}
else if (effectiveLevel === 1) {
if (Math.random() < 0.5) {
var n1 = Math.floor(Math.random() * 20) + 15;
var n2 = Math.floor(Math.random() * 15) + 5;
var sum = n1 + n2;
var displayedSum = isTrue ? sum : sum + [2, -2, 1, -1][Math.floor(Math.random() * 4)];
qStr = n1 + " + " + n2 + " = " + displayedSum;
} else {
var n1 = Math.floor(Math.random() * 5) + 4;
var n2 = Math.floor(Math.random() * 5) + 3;
var mult = n1 * n2;
var displayedMult = isTrue ? mult : mult + [1, -1, 2, -2][Math.floor(Math.random() * 4)];
qStr = n1 + " × " + n2 + " = " + displayedMult;
}
ans = isTrue ? "TRUE" : "FALSE";
}
else if (effectiveLevel === 2) {
var n1 = Math.floor(Math.random() * 6) + 4;
var n2 = Math.floor(Math.random() * 6) + 4;
var n3 = Math.floor(Math.random() * 15) + 5;
var realAns = (n1 * n2) + n3;
var trap = [2, -2, 3, -3][Math.floor(Math.random() * 4)];
var displayedAns = isTrue ? realAns : realAns + trap;
qStr = n1 + " × " + n2 + " + " + n3 + " = " + displayedAns;
ans = isTrue ? "TRUE" : "FALSE";
}
else if (effectiveLevel === 3) {
if (Math.random() < 0.5) {
var n1 = Math.floor(Math.random() * 5) + 10;
var n2 = Math.floor(Math.random() * 5) + 5;
var mult = n1 * n2;
var trap = [4, -4, 2, -2][Math.floor(Math.random() * 4)];
var displayedMult = isTrue ? mult : mult + trap;
qStr = n1 + " × " + n2 + " = " + displayedMult;
} else {
var n1 = Math.floor(Math.random() * 5) + 8;
var n2 = Math.floor(Math.random() * 5) + 8;
var n3 = Math.floor(Math.random() * 20) + 15;
var realAns = (n1 * n2) - n3;
var trap = [2, -2, 4, -4][Math.floor(Math.random() * 4)];
var displayedAns = isTrue ? realAns : realAns + trap;
qStr = n1 + " × " + n2 + " - " + n3 + " = " + displayedAns;
}
ans = isTrue ? "TRUE" : "FALSE";
}
else {
var n1 = Math.floor(Math.random() * 8) + 12;
var n2 = Math.floor(Math.random() * 5) + 6;
var n3 = Math.floor(Math.random() * 30) + 20;
var realAns = (n1 * n2) - n3;
var trap = [2, -2, 4, -4, 6, -6][Math.floor(Math.random() * 6)];
var displayedAns = isTrue ? realAns : realAns + trap;
qStr = n1 + " × " + n2 + " - " + n3 + " = " + displayedAns;
ans = isTrue ? "TRUE" : "FALSE";
}
return { q: qStr, a: ans };
}
// 🎮 GET ROUND ENGINE & FOMO DATA
function getRound(ss, phone) {
var sheet = ss.getSheetByName("Game_Rounds");
var data = sheet.getDataRange().getValues();
var now = new Date().getTime();
var activeRound = null;
for (var i = data.length - 1; i >= 1; i--) {
var status = data[i][2];
if (status === "Active" || status === "Processing") {
var roundTimestamp = new Date(data[i][1]).getTime();
if (now - roundTimestamp < ROUND_DURATION) {
if(!activeRound) activeRound = { roundId: data[i][0], timestamp: roundTimestamp };
} else {
sheet.getRange(i + 1, 3).setValue("Completed");
}
}
}
if (!activeRound) {
var newRoundId = "RND-" + Math.floor(Math.random() * 1000000);
var createTime = new Date();
sheet.appendRow([newRoundId, createTime, "Active", 0]);
activeRound = { roundId: newRoundId, timestamp: createTime.getTime() };
}
var bSheet = ss.getSheetByName("Bets");
var bData = bSheet.getDataRange().getValues();
var personalHistory = [];
var globalFomo = [];
// 🔥 BUG FIXED: Real FOMO Generation logic (Corrected Array Index for Amount)
var recentCount = 0;
for (var k = bData.length - 1; k >= 1; k--) {
if(bData[k][6] === "WON") {
var fomoUid = bData[k][2].toString().substring(6) || "USER";
var fomoAmt = parseFloat(bData[k][7]); // 🔥 FIXED: Index 7 is the correct Payout Amount column
globalFomo.push({ uid: "U-" + fomoUid, action: "won", amount: fomoAmt });
recentCount++;
if(recentCount >= 10) break;
}
}
for (var j = bData.length - 1; j >= 1; j--) {
if (bData[j][2].toString() === phone.toString()) {
var qParts = bData[j][3].toString().split("|");
personalHistory.push({
roundId: bData[j][1], question: qParts[0] || "", correctAns: qParts[1] || "",
selectedAns: bData[j][4].toString().toUpperCase(), amount: bData[j][5], status: bData[j][6]
});
if (personalHistory.length >= 30) break;
}
}
return { success: true, round: activeRound, history: personalHistory, serverTime: new Date().getTime(), fomoData: globalFomo };
}
// 💰 PLACE IQ BET
function placeIqBet(ss, req) {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return { success: false, message: "System busy." };
try {
var amount = parseFloat(req.amount);
if (amount < 5 || amount > 100) return { success: false, message: "Bet limit is ₹5 to ₹100." };
var rSheet = ss.getSheetByName("Game_Rounds");
var rData = rSheet.getDataRange().getValues();
var rIndex = -1, roundTimestamp = 0;
for (var i = rData.length - 1; i >= 1; i--) {
if (rData[i][0] === req.roundId) {
rIndex = i + 1; roundTimestamp = new Date(rData[i][1]).getTime(); break;
}
}
if (rIndex === -1) return { success: false, message: "Round not found." };
if (new Date().getTime() - roundTimestamp > BETTING_WINDOW) return { success: false, message: "Betting locked for this round!" };
var uSheet = ss.getSheetByName("Users");
var uData = uSheet.getDataRange().getValues();
var uIndex = -1, playDeducted = 0, winDeducted = 0, userStreak = 0;
for (var j = 1; j < uData.length; j++) {
if (uData[j][2].toString() === req.phone.toString()) {
uIndex = j + 1;
var playBal = parseFloat(uData[j][4]) || 0;
var winBal = parseFloat(uData[j][5]) || 0;
userStreak = parseInt(uData[j][6]) || 0;
if ((playBal + winBal) < amount) return { success: false, message: "Insufficient Balance!" };
if (playBal >= amount) {
playDeducted = amount; winDeducted = 0;
uSheet.getRange(uIndex, 5).setValue((playBal - amount).toFixed(2));
} else {
playDeducted = playBal; winDeducted = amount - playBal;
uSheet.getRange(uIndex, 5).setValue(0.00);
uSheet.getRange(uIndex, 6).setValue((winBal - winDeducted).toFixed(2));
}
break;
}
}
var mathQ = generateMathQuestion(amount, userStreak);
var betId = "B-" + Math.floor(Math.random() * 1000000);
var bSheet = ss.getSheetByName("Bets");
bSheet.appendRow([betId, req.roundId, req.phone, mathQ.q + "|" + mathQ.a, "PENDING", amount.toFixed(2), "Locked", 0, new Date(), playDeducted.toFixed(2), winDeducted.toFixed(2)]);
SpreadsheetApp.flush();
return { success: true, betId: betId, question: mathQ.q };
} finally {
lock.releaseLock();
}
}
// ✔️ SUBMIT ANSWER
function submitIqAnswer(ss, req) {
var lock = LockService.getScriptLock();
if (!lock.tryLock(3000)) return { success: false, message: "Wait..." };
try {
var bSheet = ss.getSheetByName("Bets");
var bData = bSheet.getDataRange().getValues();
for (var i = bData.length - 1; i >= 1; i--) {
if (bData[i][0] === req.betId && bData[i][2].toString() === req.phone.toString()) {
if(bData[i][4] === "PENDING") {
bSheet.getRange(i + 1, 5).setValue(String(req.answer).toUpperCase());
return { success: true };
}
return { success: false, message: "Already answered." };
}
}
return { success: false, message: "Bet not found." };
} finally {
lock.releaseLock();
}
}
// 🚫 TAB KILL / CHEAT DETECTOR
function cheatDetected(ss, req) {
var lock = LockService.getScriptLock();
if (!lock.tryLock(3000)) return { success: false };
try {
var bSheet = ss.getSheetByName("Bets");
var bData = bSheet.getDataRange().getValues();
for (var i = bData.length - 1; i >= 1; i--) {
if (bData[i][0] === req.betId && bData[i][2].toString() === req.phone.toString()) {
bSheet.getRange(i + 1, 5).setValue("CHEATED");
bSheet.getRange(i + 1, 7).setValue("Lost");
break;
}
}
var uSheet = ss.getSheetByName("Users");
var uData = uSheet.getDataRange().getValues();
for (var j = 1; j < uData.length; j++) {
if (uData[j][2].toString() === req.phone.toString()) {
var warnings = (parseInt(uData[j][7]) || 0) + 1;
uSheet.getRange(j + 1, 8).setValue(warnings);
if(warnings >= 3) uSheet.getRange(j + 1, 11).setValue("Blocked");
break;
}
}
SpreadsheetApp.flush();
return { success: true };
} finally {
lock.releaseLock();
}
}
// 🏆 RESOLVE ROUND & EXACT WALLET MATH (Proportional Refunds + Profit to Win)
function getRoundResult(ss, req) {
var lock = LockService.getScriptLock();
if (!lock.tryLock(8000)) return { success: false, message: "Resolving..." };
try {
var rSheet = ss.getSheetByName("Game_Rounds");
var rData = rSheet.getDataRange().getValues();
var rIndex = -1, isProcessed = false;
for (var i = rData.length - 1; i >= 1; i--) {
if (rData[i][0] === req.roundId) {
if (rData[i][2] === "Completed") isProcessed = true;
rIndex = i + 1;
break;
}
}
if (rIndex === -1) return { success: false };
var bSheet = ss.getSheetByName("Bets");
var userResult = null;
if (!isProcessed) {
var bData = bSheet.getDataRange().getValues();
var uSheet = ss.getSheetByName("Users");
var uData = uSheet.getDataRange().getValues();
var totalPool = 0, distributed = 0, totalPlayers = 0;
for (var j = 1; j < bData.length; j++) {
if (bData[j][1] === req.roundId && (bData[j][6] === "Locked" || bData[j][6] === "Lost" && bData[j][4]==="CHEATED")) {
totalPlayers++;
var betRow = j + 1;
var phone = bData[j][2].toString();
var qData = bData[j][3].toString().split("|");
var correctAns = String(qData[1]).trim().toUpperCase();
var selectedAns = String(bData[j][4]).trim().toUpperCase();
var amt = parseFloat(bData[j][5]);
var pDed = parseFloat(bData[j][9]) || 0;
var wDed = parseFloat(bData[j][10]) || 0;
totalPool += amt;
var status = "LOST", payout = 0;
if (selectedAns === "CHEATED") {
status = "LOST";
} else if (selectedAns === "PENDING" || selectedAns === "TIME_OUT") {
status = "REFUNDED";
payout = amt * 0.80;
bSheet.getRange(betRow, 5).setValue("TIME_OUT");
distributed += payout;
} else if (selectedAns === correctAns) {
status = "WON";
payout = amt * 2.00;
distributed += payout;
}
bSheet.getRange(betRow, 7).setValue(status);
bSheet.getRange(betRow, 8).setValue(payout.toFixed(2));
for (var m = 1; m < uData.length; m++) {
if (uData[m][2].toString() === phone) {
var pBal = parseFloat(uSheet.getRange(m + 1, 5).getValue()) || 0;
var wBal = parseFloat(uSheet.getRange(m + 1, 6).getValue()) || 0;
var streak = parseInt(uSheet.getRange(m + 1, 7).getValue()) || 0;
var totalP = parseInt(uSheet.getRange(m + 1, 9).getValue()) || 0;
if (status === "WON") {
var profitAmount = payout - amt;
uSheet.getRange(m + 1, 5).setValue((pBal + pDed).toFixed(2));
uSheet.getRange(m + 1, 6).setValue((wBal + wDed + profitAmount).toFixed(2));
uSheet.getRange(m + 1, 7).setValue(streak + 1);
} else if (status === "REFUNDED") {
var playRefund = pDed * 0.80;
var winRefund = wDed * 0.80;
uSheet.getRange(m + 1, 5).setValue((pBal + playRefund).toFixed(2));
uSheet.getRange(m + 1, 6).setValue((wBal + winRefund).toFixed(2));
} else {
uSheet.getRange(m + 1, 7).setValue(0);
}
uSheet.getRange(m + 1, 9).setValue(totalP + 1);
break;
}
}
if (phone === req.phone.toString()) userResult = status.toUpperCase();
} else if (bData[j][1] === req.roundId && bData[j][2].toString() === req.phone.toString()) {
userResult = bData[j][6].toUpperCase();
}
}
rSheet.getRange(rIndex, 3).setValue("Completed");
rSheet.getRange(rIndex, 4).setValue(totalPlayers);
if(totalPlayers > 0) {
var adminProfit = totalPool - distributed;
ss.getSheetByName("Admin_Profit").appendRow([req.roundId, totalPool.toFixed(2), adminProfit.toFixed(2), distributed.toFixed(2), new Date()]);
}
SpreadsheetApp.flush();
} else {
var bData2 = bSheet.getDataRange().getValues();
for (var k = bData2.length - 1; k >= 1; k--) {
if (bData2[k][1] === req.roundId && bData2[k][2].toString() === req.phone.toString()) {
userResult = bData2[k][6].toUpperCase(); break;
}
}
}
return { success: true, result: userResult };
} finally {
lock.releaseLock();
}
}
// 🟢 ADMIN APPROVAL (onEdit Trigger) - Deposits & Referrals
function onEdit(e) {
if (!e || !e.range) return;
var sheet = e.range.getSheet();
if (sheet.getName() !== "Transactions") return;
var col = e.range.getColumn();
var row = e.range.getRow();
if (col === 6 && row > 1) {
var status = sheet.getRange(row, 6).getDisplayValue().trim();
var oldStatus = e.oldValue ? e.oldValue.toString().trim() : "";
var type = sheet.getRange(row, 4).getDisplayValue().trim();
var phone = sheet.getRange(row, 3).getDisplayValue().trim();
var amount = parseFloat(sheet.getRange(row, 5).getValue());
var lock = LockService.getScriptLock();
if (!lock.tryLock(10000)) return;
try {
if (status === "Approved" && oldStatus !== "Approved") {
if (type === "Deposit" && !isNaN(amount) && amount > 0) {
var usersSheet = e.source.getSheetByName("Users");
var usersData = usersSheet.getDataRange().getDisplayValues();
for (var i = 1; i < usersData.length; i++) {
if (usersData[i][2].toString().trim() === phone) {
var currentBal = parseFloat(usersSheet.getRange(i + 1, 5).getValue()) || 0;
usersSheet.getRange(i + 1, 5).setValue((currentBal + amount).toFixed(2));
if (amount >= 100) {
var referredByPhone = usersData[i][13] ? usersData[i][13].toString().trim() : "";
if (referredByPhone !== "" && referredByPhone !== "BONUS_PAID") {
for (var r = 1; r < usersData.length; r++) {
if (usersData[r][2].toString().trim() === referredByPhone) {
var refBal = parseFloat(usersSheet.getRange(r + 1, 5).getValue()) || 0;
usersSheet.getRange(r + 1, 5).setValue((refBal + 50).toFixed(2));
var bonusTxnId = "TXN" + Math.floor(Math.random() * 900000 + 100000);
e.source.getSheetByName("Transactions").appendRow([
bonusTxnId, new Date(), referredByPhone, "Referral Bonus", "50.00", "Approved", "From: " + phone
]);
usersSheet.getRange(i + 1, 14).setValue("BONUS_PAID");
break;
}
}
}
}
break;
}
}
}
}
else if (status === "Rejected" && oldStatus !== "Rejected") {
if (type === "Withdrawal" && !isNaN(amount) && amount < 0) {
var usersSheet = e.source.getSheetByName("Users");
var usersData = usersSheet.getDataRange().getDisplayValues();
for (var i = 1; i < usersData.length; i++) {
if (usersData[i][2].toString().trim() === phone) {
var currentWin = parseFloat(usersSheet.getRange(i + 1, 6).getValue()) || 0;
usersSheet.getRange(i + 1, 6).setValue((currentWin + Math.abs(amount)).toFixed(2));
break;
}
}
}
}
SpreadsheetApp.flush();
} finally {
lock.releaseLock();
}
}
}
// 💸 DEPOSIT & WITHDRAW REQUESTS
function depositFunds(ss, req) {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return { success: false, message: "System busy." };
try {
var amount = parseFloat(req.amount);
if (amount < 50 || amount > 1000) return { success: false, message: "Limit ₹50 to ₹1000." };
var tSheet = ss.getSheetByName("Transactions");
var txnId = "TXN" + Math.floor(Math.random() * 900000 + 100000);
tSheet.appendRow([txnId, new Date(), req.phone, "Deposit", amount.toFixed(2), "Pending", req.utr]);
sendTelegramAlert(txnId, "Deposit", amount.toFixed(2), req.phone, req.utr);
return { success: true };
} finally { lock.releaseLock(); }
}
function withdrawFunds(ss, req) {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return { success: false, message: "Server busy." };
try {
var amount = parseFloat(req.amount);
if (amount < 100 || amount > 2000) return { success: false, message: "Limit ₹100 to ₹2000." };
var tSheet = ss.getSheetByName("Transactions");
var tData = tSheet.getDataRange().getValues();
var todayStr = new Date().toDateString();
var dailyCount = 0;
for (var j = tData.length - 1; j >= 1; j--) {
if (tData[j][2].toString() === req.phone.toString() && tData[j][3] === "Withdrawal") {
if (new Date(tData[j][1]).toDateString() === todayStr) dailyCount++;
}
}
if (dailyCount >= 1) return { success: false, message: "Only 1 withdrawal allowed per day." };
var uSheet = ss.getSheetByName("Users");
var uData = uSheet.getDataRange().getValues();
for (var i = 1; i < uData.length; i++) {
if (uData[i][2].toString() === req.phone.toString()) {
var playBal = parseFloat(uData[i][4]) || 0;
var winBal = parseFloat(uData[i][5]) || 0;
if (playBal >= 5) return { success: false, message: "⚠️ Play remaining Play Balance (₹" + playBal.toFixed(2) + ") first!" };
if (winBal < amount) return { success: false, message: "Insufficient Winning Balance!" };
uSheet.getRange(i + 1, 4).setValue(req.upiId);
uSheet.getRange(i + 1, 6).setValue((winBal - amount).toFixed(2));
var txnId = "TXN" + Math.floor(Math.random() * 900000 + 100000);
tSheet.appendRow([txnId, new Date(), req.phone, "Withdrawal", "-" + amount.toFixed(2), "Pending", req.upiId]);
SpreadsheetApp.flush();
sendTelegramAlert(txnId, "Withdrawal", "-" + amount.toFixed(2), req.phone, req.upiId);
return { success: true };
}
}
} finally { lock.releaseLock(); }
}
// 🤖 TELEGRAM BOT NOTIFICATIONS
function sendTelegramAlert(txnId, type, amount, phone, ref) {
var emoji = (type === "Deposit") ? "📥" : "📤";
var text = "🧠 *IQ Rewards Alert*\n\n🚨 New " + type + " Request!\n" + emoji + " Amount: ₹" + amount + "\n📱 Phone: " + phone + "\n🔖 Details: " + ref + "\n🆔 Txn ID: " + txnId;
var keyboard = { "inline_keyboard": [ [ { "text": "✅ Approve", "callback_data": "APPROVE_" + txnId }, { "text": "❌ Reject", "callback_data": "REJECT_" + txnId } ] ] };
try {
UrlFetchApp.fetch("https://api.telegram.org/bot" + TELEGRAM_TOKEN + "/sendMessage", {
"method": "post", "contentType": "application/json",
"payload": JSON.stringify({ "chat_id": ADMIN_CHAT_ID, "text": text, "reply_markup": keyboard })
});
} catch(e) { }
}
function handleTelegramAction(callback_query) {
var data = callback_query.data;
var messageId = callback_query.message.message_id;
var action = data.split("_")[0];
var txnId = data.split("_")[1];
var lock = LockService.getScriptLock();
if (!lock.tryLock(10000)) return ContentService.createTextOutput("System Busy");
try {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var tSheet = ss.getSheetByName("Transactions");
var tData = tSheet.getDataRange().getValues();
var rowIndex = -1, phone = "", amount = 0, type = "";
for (var i = 1; i < tData.length; i++) {
if (tData[i][0].toString().trim() === txnId.toString().trim()) {
rowIndex = i + 1; phone = tData[i][2].toString().trim(); type = tData[i][3].toString().trim(); amount = parseFloat(tData[i][4]); break;
}
}
if (rowIndex !== -1) {
var currentStatus = tSheet.getRange(rowIndex, 6).getValue().toString().trim();
if (currentStatus === "Pending") {
var newStatus = (action === "APPROVE") ? "Approved" : "Rejected";
tSheet.getRange(rowIndex, 6).setValue(newStatus);
var uSheet = ss.getSheetByName("Users");
var uData = uSheet.getDataRange().getValues();
if (newStatus === "Approved" && type === "Deposit") {
for (var j = 1; j < uData.length; j++) {
if (uData[j][2].toString().trim() === phone) {
var currentPlay = parseFloat(uSheet.getRange(j + 1, 5).getValue()) || 0;
uSheet.getRange(j + 1, 5).setValue((currentPlay + amount).toFixed(2));
if (amount >= 100) {
var referredByPhone = uData[j][13] ? uData[j][13].toString().trim() : "";
if (referredByPhone !== "" && referredByPhone !== "BONUS_PAID") {
for (var r = 1; r < uData.length; r++) {
if (uData[r][2].toString().trim() === referredByPhone) {
var refBal = parseFloat(uSheet.getRange(r + 1, 5).getValue()) || 0;
uSheet.getRange(r + 1, 5).setValue((refBal + 50).toFixed(2));
var bonusTxnId = "TXN" + Math.floor(Math.random() * 900000 + 100000);
tSheet.appendRow([bonusTxnId, new Date(), referredByPhone, "Referral Bonus", "50.00", "Approved", "From: " + phone]);
uSheet.getRange(j + 1, 14).setValue("BONUS_PAID");
break;
}
}
}
}
break;
}
}
} else if (newStatus === "Rejected" && type === "Withdrawal") {
for (var j = 1; j < uData.length; j++) {
if (uData[j][2].toString().trim() === phone) {
var currentWin = parseFloat(uSheet.getRange(j + 1, 6).getValue()) || 0;
uSheet.getRange(j + 1, 6).setValue((currentWin + Math.abs(amount)).toFixed(2));
break;
}
}
}
SpreadsheetApp.flush();
var statusEmoji = (newStatus === "Approved") ? "🟢" : "🔴";
var updatedText = callback_query.message.text + "\n\n" + statusEmoji + " Status: " + newStatus;
UrlFetchApp.fetch("https://api.telegram.org/bot" + TELEGRAM_TOKEN + "/editMessageText", {
"method": "post", "contentType": "application/json",
"payload": JSON.stringify({ chat_id: ADMIN_CHAT_ID, message_id: messageId, text: updatedText })
});
}
}
UrlFetchApp.fetch("https://api.telegram.org/bot" + TELEGRAM_TOKEN + "/answerCallbackQuery", {
"method": "post", "payload": { "callback_query_id": callback_query.id }
});
} finally { lock.releaseLock(); }
return ContentService.createTextOutput("OK");
}
// 🧹 AUTO DATABASE CLEANUP
function cleanupGameHistory() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var rSheet = ss.getSheetByName("Game_Rounds");
var rLast = rSheet.getLastRow();
if (rLast > 100) rSheet.deleteRows(2, rLast - 100);
var bSheet = ss.getSheetByName("Bets");
var bLast = bSheet.getLastRow();
if (bLast > 300) bSheet.deleteRows(2, bLast - 300);
}
function userLifecycleManager() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Users");
var inSheet = ss.getSheetByName("Inactive_Users");
var arSheet = ss.getSheetByName("Archive_Users");
var data = sheet.getDataRange().getValues();
var now = new Date().getTime();
for (var i = data.length - 1; i >= 1; i--) {
var lastActive = new Date(data[i][9]).getTime();
var daysInactive = (now - lastActive) / (1000 * 3600 * 24);
if (daysInactive >= 50) sheet.deleteRow(i + 1);
else if (daysInactive >= 30) { arSheet.appendRow(data[i]); sheet.deleteRow(i + 1); }
else if (daysInactive >= 15) { inSheet.appendRow(data[i]); sheet.deleteRow(i + 1); }
}
}