Spaces:
Running
Running
Upload 38 files
Browse files- api.js +75 -0
- bot.js +48 -26
- games/Checkers.js +167 -0
- games/Chess.js +178 -0
- handlers/blackjack.js +30 -67
- handlers/boardHandler.js +33 -91
- handlers/combat.js +4 -0
- handlers/games.js +22 -79
- handlers/inlineHandler.js +59 -152
- handlers/strategyGames.js +159 -0
- models/User.js +4 -0
- utils/editMsg.js +53 -0
- webapp/index.html +592 -570
api.js
CHANGED
|
@@ -202,6 +202,81 @@ app.post('/claim', auth, async (req, res) => {
|
|
| 202 |
}
|
| 203 |
});
|
| 204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
// ── Compat: GET /daily/status ──────────────────────────────────────────────
|
| 206 |
app.get('/daily/status', auth, async (req, res) => {
|
| 207 |
req.query.type = 'daily';
|
|
|
|
| 202 |
}
|
| 203 |
});
|
| 204 |
|
| 205 |
+
// ── GET /leaderboard?type=bank|wallet|xp|kills|robs&period=daily|weekly|monthly|all ──
|
| 206 |
+
app.get('/leaderboard', auth, async (req, res) => {
|
| 207 |
+
try {
|
| 208 |
+
const { type = 'wallet', period = 'all' } = req.query;
|
| 209 |
+
|
| 210 |
+
// Build sort field
|
| 211 |
+
const sortFields = {
|
| 212 |
+
bank: { bank: -1 },
|
| 213 |
+
wallet: { wallet: -1 },
|
| 214 |
+
xp: { xp: -1 },
|
| 215 |
+
kills: { kills: -1 },
|
| 216 |
+
robs: { robs: -1 },
|
| 217 |
+
};
|
| 218 |
+
const sort = sortFields[type] || { wallet: -1 };
|
| 219 |
+
|
| 220 |
+
// Period filter (for daily/weekly/monthly we filter by lastDaily or recent activity)
|
| 221 |
+
let dateFilter = {};
|
| 222 |
+
const now = new Date();
|
| 223 |
+
if (period === 'daily') {
|
| 224 |
+
const dayStart = new Date(now.toISOString().split('T')[0]);
|
| 225 |
+
dateFilter = { lastDaily: { $gte: dayStart } };
|
| 226 |
+
} else if (period === 'weekly') {
|
| 227 |
+
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
| 228 |
+
dateFilter = { lastDaily: { $gte: weekAgo } };
|
| 229 |
+
} else if (period === 'monthly') {
|
| 230 |
+
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
| 231 |
+
dateFilter = { lastDaily: { $gte: monthAgo } };
|
| 232 |
+
}
|
| 233 |
+
// 'all' = no date filter
|
| 234 |
+
|
| 235 |
+
const users = await User.find(dateFilter)
|
| 236 |
+
.sort(sort)
|
| 237 |
+
.limit(50)
|
| 238 |
+
.select('userId username wallet bank xp level kills robs totalEarned')
|
| 239 |
+
.lean();
|
| 240 |
+
|
| 241 |
+
// Resolve usernames via TG client for users missing username
|
| 242 |
+
if (global._tgClient) {
|
| 243 |
+
for (const u of users) {
|
| 244 |
+
if (!u.username) {
|
| 245 |
+
try {
|
| 246 |
+
const ent = await global._tgClient.getEntity(u.userId);
|
| 247 |
+
u.username = ent.username || ent.firstName || `User${u.userId.slice(-4)}`;
|
| 248 |
+
} catch { u.username = `User${u.userId.slice(-4)}`; }
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
} else {
|
| 252 |
+
for (const u of users) {
|
| 253 |
+
if (!u.username) u.username = `User${u.userId.slice(-4)}`;
|
| 254 |
+
}
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
res.json({ ok: true, leaderboard: users, type, period });
|
| 258 |
+
} catch (err) {
|
| 259 |
+
console.error('[API] /leaderboard error:', err);
|
| 260 |
+
res.status(500).json({ error: 'Server error' });
|
| 261 |
+
}
|
| 262 |
+
});
|
| 263 |
+
|
| 264 |
+
// ── GET /me — get current user stats ──
|
| 265 |
+
app.get('/me', auth, async (req, res) => {
|
| 266 |
+
try {
|
| 267 |
+
const userId = req.tgUser.id.toString();
|
| 268 |
+
let user = await User.findOne({ userId }).lean();
|
| 269 |
+
if (!user) {
|
| 270 |
+
user = await User.create({ userId, username: req.tgUser.username });
|
| 271 |
+
user = user.toObject();
|
| 272 |
+
}
|
| 273 |
+
res.json({ ok: true, user });
|
| 274 |
+
} catch (err) {
|
| 275 |
+
console.error('[API] /me error:', err);
|
| 276 |
+
res.status(500).json({ error: 'Server error' });
|
| 277 |
+
}
|
| 278 |
+
});
|
| 279 |
+
|
| 280 |
// ── Compat: GET /daily/status ──────────────────────────────────────────────
|
| 281 |
app.get('/daily/status', auth, async (req, res) => {
|
| 282 |
req.query.type = 'daily';
|
bot.js
CHANGED
|
@@ -7,12 +7,9 @@ const connectDB = require('./db/mongo');
|
|
| 7 |
const User = require('./models/User');
|
| 8 |
const { startApi } = require('./api');
|
| 9 |
|
| 10 |
-
// Env Check
|
| 11 |
const requiredEnv = ['API_ID', 'API_HASH', 'BOT_TOKEN', 'MONGO_URI'];
|
| 12 |
const missing = requiredEnv.filter(k => !process.env[k]);
|
| 13 |
-
if (missing.length > 0) {
|
| 14 |
-
console.warn(`⚠️ WARNING: Missing environment variables: ${missing.join(", ")}`);
|
| 15 |
-
}
|
| 16 |
|
| 17 |
// Handlers
|
| 18 |
const economy = require('./handlers/economy');
|
|
@@ -25,6 +22,7 @@ const boardGames = require('./handlers/boardGames');
|
|
| 25 |
const boardHandler = require('./handlers/boardHandler');
|
| 26 |
const blackjack = require('./handlers/blackjack');
|
| 27 |
const cardGame = require('./handlers/cardGameHandler');
|
|
|
|
| 28 |
|
| 29 |
const apiId = parseInt(process.env.API_ID || 0);
|
| 30 |
const apiHash = process.env.API_HASH || "";
|
|
@@ -33,12 +31,10 @@ const stringSession = new StringSession("");
|
|
| 33 |
|
| 34 |
(async () => {
|
| 35 |
await connectDB();
|
| 36 |
-
startApi();
|
| 37 |
const client = new TelegramClient(stringSession, apiId, apiHash, { connectionRetries: 5 });
|
| 38 |
await client.start({ botAuthToken: botToken });
|
| 39 |
console.log("Bot is running...");
|
| 40 |
-
|
| 41 |
-
// Expose client globally so api.js can send messages via MTProto
|
| 42 |
global._tgClient = client;
|
| 43 |
|
| 44 |
const isAlive = async (event) => {
|
|
@@ -46,10 +42,7 @@ const stringSession = new StringSession("");
|
|
| 46 |
const user = await User.findOne({ userId });
|
| 47 |
if (user && user.isDead) {
|
| 48 |
if (Date.now() - (user.lastDeath || 0) > 4 * 60 * 60 * 1000) {
|
| 49 |
-
user.isDead = false;
|
| 50 |
-
user.health = 100;
|
| 51 |
-
await user.save();
|
| 52 |
-
return true;
|
| 53 |
}
|
| 54 |
await event.message.respond({ message: "💀 You are dead! Use /revive or wait 4 hours." });
|
| 55 |
return false;
|
|
@@ -57,25 +50,46 @@ const stringSession = new StringSession("");
|
|
| 57 |
return true;
|
| 58 |
};
|
| 59 |
|
| 60 |
-
// ─── Handle normal text messages ──────────────────────────────────────────
|
| 61 |
client.addEventHandler(async (event) => {
|
| 62 |
const text = event.message.message || '';
|
| 63 |
if (!text) return;
|
| 64 |
|
| 65 |
if (text === "/start") {
|
| 66 |
-
await event.message.respond({ message:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
}
|
| 68 |
|
| 69 |
if (text === "/wallet") {
|
| 70 |
const userId = event.message.senderId.toString();
|
| 71 |
let user = await User.findOne({ userId });
|
| 72 |
-
if (!user) user = await User.create({ userId, username: event.message.sender
|
| 73 |
-
await event.message.respond({ message: `👤 **
|
| 74 |
}
|
| 75 |
|
| 76 |
if (text === "/daily") await economy.daily(client, event);
|
| 77 |
if (text === "/missions") await economy.missions(client, event);
|
| 78 |
-
|
| 79 |
if (text.startsWith("/dp")) await economy.deposit(client, event);
|
| 80 |
if (text.startsWith("/wd")) await economy.withdraw(client, event);
|
| 81 |
|
|
@@ -83,19 +97,29 @@ const stringSession = new StringSession("");
|
|
| 83 |
if (text.startsWith("/rob")) { if (await isAlive(event)) await combat.rob(client, event); }
|
| 84 |
if (text === "/revive") await combat.revive(client, event);
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
if (text.startsWith("/cards")) { if (await isAlive(event)) await cardGame.initCardGame(client, event); }
|
| 87 |
if (text.startsWith("/hack")) { if (await isAlive(event)) await multiHack.initHack(client, event); }
|
| 88 |
if (text === "/join") {
|
| 89 |
await multiHack.joinHack(client, event);
|
| 90 |
await cardGame.joinCardGame(client, event);
|
|
|
|
| 91 |
}
|
| 92 |
if (text.startsWith("/guess")) await multiHack.processGuess(client, event);
|
| 93 |
if (text.startsWith("/flip")) await cardGame.processFlip(client, event);
|
| 94 |
|
|
|
|
| 95 |
if (text.startsWith("/xox")) { if (await isAlive(event)) await boardGames.startXOX(client, event); }
|
| 96 |
if (text.startsWith("/c4")) { if (await isAlive(event)) await boardGames.startC4(client, event); }
|
| 97 |
if (text.startsWith("/dice")) { if (await isAlive(event)) await games.startDice(client, event); }
|
| 98 |
|
|
|
|
| 99 |
if (text.startsWith("/bj")) { if (await isAlive(event)) await blackjack.startBJ(client, event); }
|
| 100 |
if (text === "/slots") await inlineGames.startSlots(client, event);
|
| 101 |
if (text === "/mines") await inlineGames.startMines(client, event);
|
|
@@ -105,18 +129,16 @@ const stringSession = new StringSession("");
|
|
| 105 |
|
| 106 |
}, new NewMessage({}));
|
| 107 |
|
| 108 |
-
//
|
| 109 |
client.addEventHandler(async (update) => {
|
| 110 |
if (update instanceof Api.UpdateBotCallbackQuery) {
|
| 111 |
-
try { await games.handleCallback(client, update); } catch (e) { console.error('[CB] games
|
| 112 |
-
try { await games.handleDiceCallback(client, update); } catch (e) { console.error('[CB]
|
| 113 |
-
try { await inlineHandler.handleInlineCallback(client, update); } catch (e) { console.error('[CB]
|
| 114 |
-
try { await boardHandler.handleBoardCallback(client, update); } catch (e) { console.error('[CB]
|
| 115 |
-
try { await blackjack.handleBJCallback(client, update); } catch (e) { console.error('[CB]
|
| 116 |
-
|
| 117 |
-
try {
|
| 118 |
-
await client.invoke(new Api.messages.SetBotCallbackAnswer({ queryId: update.queryId, cacheTime: 1 }));
|
| 119 |
-
} catch (e) { }
|
| 120 |
}
|
| 121 |
});
|
| 122 |
})();
|
|
|
|
| 7 |
const User = require('./models/User');
|
| 8 |
const { startApi } = require('./api');
|
| 9 |
|
|
|
|
| 10 |
const requiredEnv = ['API_ID', 'API_HASH', 'BOT_TOKEN', 'MONGO_URI'];
|
| 11 |
const missing = requiredEnv.filter(k => !process.env[k]);
|
| 12 |
+
if (missing.length > 0) console.warn(`⚠️ Missing env: ${missing.join(", ")}`);
|
|
|
|
|
|
|
| 13 |
|
| 14 |
// Handlers
|
| 15 |
const economy = require('./handlers/economy');
|
|
|
|
| 22 |
const boardHandler = require('./handlers/boardHandler');
|
| 23 |
const blackjack = require('./handlers/blackjack');
|
| 24 |
const cardGame = require('./handlers/cardGameHandler');
|
| 25 |
+
const strategy = require('./handlers/strategyGames');
|
| 26 |
|
| 27 |
const apiId = parseInt(process.env.API_ID || 0);
|
| 28 |
const apiHash = process.env.API_HASH || "";
|
|
|
|
| 31 |
|
| 32 |
(async () => {
|
| 33 |
await connectDB();
|
| 34 |
+
startApi();
|
| 35 |
const client = new TelegramClient(stringSession, apiId, apiHash, { connectionRetries: 5 });
|
| 36 |
await client.start({ botAuthToken: botToken });
|
| 37 |
console.log("Bot is running...");
|
|
|
|
|
|
|
| 38 |
global._tgClient = client;
|
| 39 |
|
| 40 |
const isAlive = async (event) => {
|
|
|
|
| 42 |
const user = await User.findOne({ userId });
|
| 43 |
if (user && user.isDead) {
|
| 44 |
if (Date.now() - (user.lastDeath || 0) > 4 * 60 * 60 * 1000) {
|
| 45 |
+
user.isDead = false; user.health = 100; await user.save(); return true;
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
await event.message.respond({ message: "💀 You are dead! Use /revive or wait 4 hours." });
|
| 48 |
return false;
|
|
|
|
| 50 |
return true;
|
| 51 |
};
|
| 52 |
|
|
|
|
| 53 |
client.addEventHandler(async (event) => {
|
| 54 |
const text = event.message.message || '';
|
| 55 |
if (!text) return;
|
| 56 |
|
| 57 |
if (text === "/start") {
|
| 58 |
+
await event.message.respond({ message:
|
| 59 |
+
"🎮 **Alexagame Bot**\n\n" +
|
| 60 |
+
"💰 /daily - Claim $3000 daily reward\n" +
|
| 61 |
+
"💰 /wallet - Balance & stats\n" +
|
| 62 |
+
"⚔️ /kill (reply) - Kill for cash & XP\n" +
|
| 63 |
+
"⚔️ /rob (reply) - Rob user (risky)\n" +
|
| 64 |
+
"⚔️ /revive - Revive for $1000\n\n" +
|
| 65 |
+
"♔ /chess <bet> - Chess (2P)\n" +
|
| 66 |
+
"⛀ /checkers <bet> - Checkers/Draughts (2P)\n" +
|
| 67 |
+
"🕹️ /xox <bet> - Tic-Tac-Toe\n" +
|
| 68 |
+
"🕹️ /c4 <bet> - Connect 4\n" +
|
| 69 |
+
"🕹️ /hack <bet> <len> - PIN hack\n" +
|
| 70 |
+
"🕹️ /cards <bet> - Card flip game\n" +
|
| 71 |
+
"🕹️ /dice <bet> - Dice duel\n\n" +
|
| 72 |
+
"🃏 /bj <bet> - Blackjack\n" +
|
| 73 |
+
"🃏 /slots - Slot machine\n" +
|
| 74 |
+
"🃏 /mines - Avoid mines\n" +
|
| 75 |
+
"🃏 /coinflip - Coin flip\n" +
|
| 76 |
+
"🃏 /roulette - Russian roulette\n" +
|
| 77 |
+
"🃏 /hl - Higher or Lower\n\n" +
|
| 78 |
+
"📋 Moves: `/m e2 e4` (chess/checkers)\n" +
|
| 79 |
+
"🏳️ /surrender - Forfeit game\n" +
|
| 80 |
+
"Use /daily or /missions to open the reward mini app!"
|
| 81 |
+
});
|
| 82 |
}
|
| 83 |
|
| 84 |
if (text === "/wallet") {
|
| 85 |
const userId = event.message.senderId.toString();
|
| 86 |
let user = await User.findOne({ userId });
|
| 87 |
+
if (!user) user = await User.create({ userId, username: event.message.sender?.username });
|
| 88 |
+
await event.message.respond({ message: `👤 **Lv.${user.level}** | XP: ${user.xp}\n💰 Wallet: $${user.wallet}\n💳 Bank: $${user.bank}\n❤️ HP: ${user.health}%\n💀 Kills: ${user.kills||0} | 💸 Robs: ${user.robs||0}` });
|
| 89 |
}
|
| 90 |
|
| 91 |
if (text === "/daily") await economy.daily(client, event);
|
| 92 |
if (text === "/missions") await economy.missions(client, event);
|
|
|
|
| 93 |
if (text.startsWith("/dp")) await economy.deposit(client, event);
|
| 94 |
if (text.startsWith("/wd")) await economy.withdraw(client, event);
|
| 95 |
|
|
|
|
| 97 |
if (text.startsWith("/rob")) { if (await isAlive(event)) await combat.rob(client, event); }
|
| 98 |
if (text === "/revive") await combat.revive(client, event);
|
| 99 |
|
| 100 |
+
// Strategy games (text-based moves)
|
| 101 |
+
if (text.startsWith("/chess")) { if (await isAlive(event)) await strategy.startChess(client, event); }
|
| 102 |
+
if (text.startsWith("/checkers")) { if (await isAlive(event)) await strategy.startCheckers(client, event); }
|
| 103 |
+
if (text.startsWith("/m ")) await strategy.processMove(client, event);
|
| 104 |
+
if (text === "/surrender") await strategy.surrender(client, event);
|
| 105 |
+
|
| 106 |
+
// Multiplayer lobby games
|
| 107 |
if (text.startsWith("/cards")) { if (await isAlive(event)) await cardGame.initCardGame(client, event); }
|
| 108 |
if (text.startsWith("/hack")) { if (await isAlive(event)) await multiHack.initHack(client, event); }
|
| 109 |
if (text === "/join") {
|
| 110 |
await multiHack.joinHack(client, event);
|
| 111 |
await cardGame.joinCardGame(client, event);
|
| 112 |
+
await strategy.joinStrategy(client, event);
|
| 113 |
}
|
| 114 |
if (text.startsWith("/guess")) await multiHack.processGuess(client, event);
|
| 115 |
if (text.startsWith("/flip")) await cardGame.processFlip(client, event);
|
| 116 |
|
| 117 |
+
// Board games (inline buttons)
|
| 118 |
if (text.startsWith("/xox")) { if (await isAlive(event)) await boardGames.startXOX(client, event); }
|
| 119 |
if (text.startsWith("/c4")) { if (await isAlive(event)) await boardGames.startC4(client, event); }
|
| 120 |
if (text.startsWith("/dice")) { if (await isAlive(event)) await games.startDice(client, event); }
|
| 121 |
|
| 122 |
+
// Casino
|
| 123 |
if (text.startsWith("/bj")) { if (await isAlive(event)) await blackjack.startBJ(client, event); }
|
| 124 |
if (text === "/slots") await inlineGames.startSlots(client, event);
|
| 125 |
if (text === "/mines") await inlineGames.startMines(client, event);
|
|
|
|
| 129 |
|
| 130 |
}, new NewMessage({}));
|
| 131 |
|
| 132 |
+
// Callback queries
|
| 133 |
client.addEventHandler(async (update) => {
|
| 134 |
if (update instanceof Api.UpdateBotCallbackQuery) {
|
| 135 |
+
try { await games.handleCallback(client, update); } catch (e) { console.error('[CB] games:', e.message); }
|
| 136 |
+
try { await games.handleDiceCallback(client, update); } catch (e) { console.error('[CB] dice:', e.message); }
|
| 137 |
+
try { await inlineHandler.handleInlineCallback(client, update); } catch (e) { console.error('[CB] inline:', e.message); }
|
| 138 |
+
try { await boardHandler.handleBoardCallback(client, update); } catch (e) { console.error('[CB] board:', e.message); }
|
| 139 |
+
try { await blackjack.handleBJCallback(client, update); } catch (e) { console.error('[CB] bj:', e.message); }
|
| 140 |
+
|
| 141 |
+
try { await client.invoke(new Api.messages.SetBotCallbackAnswer({ queryId: update.queryId, cacheTime: 1 })); } catch (e) {}
|
|
|
|
|
|
|
| 142 |
}
|
| 143 |
});
|
| 144 |
})();
|
games/Checkers.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Checkers (Draughts) — Flying Kings variant
|
| 3 |
+
* 8x8 board, pieces only on dark squares.
|
| 4 |
+
* White: ⛀ (man) ⛁ (king) | Black: ⛂ (man) ⛃ (king)
|
| 5 |
+
* White moves up (row 7→0), Black moves down (row 0→7)
|
| 6 |
+
* Simplified: text-command based moves like /m a3 b4
|
| 7 |
+
*/
|
| 8 |
+
class Checkers {
|
| 9 |
+
constructor(player1, bet) {
|
| 10 |
+
this.players = [player1]; // [white, black]
|
| 11 |
+
this.bet = bet;
|
| 12 |
+
this.board = this.initBoard();
|
| 13 |
+
this.turn = 0; // 0=white, 1=black
|
| 14 |
+
this.status = 'lobby';
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
initBoard() {
|
| 18 |
+
const b = Array(8).fill(null).map(() => Array(8).fill(null));
|
| 19 |
+
// Black pieces (top, rows 0-2)
|
| 20 |
+
for (let r = 0; r < 3; r++)
|
| 21 |
+
for (let c = 0; c < 8; c++)
|
| 22 |
+
if ((r + c) % 2 === 1) b[r][c] = '⛂';
|
| 23 |
+
// White pieces (bottom, rows 5-7)
|
| 24 |
+
for (let r = 5; r < 8; r++)
|
| 25 |
+
for (let c = 0; c < 8; c++)
|
| 26 |
+
if ((r + c) % 2 === 1) b[r][c] = '⛀';
|
| 27 |
+
return b;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
addPlayer(userId) {
|
| 31 |
+
if (this.players.length < 2 && !this.players.includes(userId)) {
|
| 32 |
+
this.players.push(userId);
|
| 33 |
+
this.status = 'playing';
|
| 34 |
+
return true;
|
| 35 |
+
}
|
| 36 |
+
return false;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// Parse "a3" -> {r:5, c:0}
|
| 40 |
+
parsePos(s) {
|
| 41 |
+
if (!s || s.length < 2) return null;
|
| 42 |
+
const c = s.charCodeAt(0) - 97; // a=0
|
| 43 |
+
const r = 8 - parseInt(s[1]); // 1=row7, 8=row0
|
| 44 |
+
if (c < 0 || c > 7 || r < 0 || r > 7) return null;
|
| 45 |
+
return { r, c };
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
posStr(r, c) {
|
| 49 |
+
return String.fromCharCode(97 + c) + (8 - r);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
isOwn(r, c) {
|
| 53 |
+
const piece = this.board[r][c];
|
| 54 |
+
if (!piece) return false;
|
| 55 |
+
if (this.turn === 0) return piece === '⛀' || piece === '⛁'; // white
|
| 56 |
+
return piece === '⛂' || piece === '⛃'; // black
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
isEnemy(r, c) {
|
| 60 |
+
const piece = this.board[r][c];
|
| 61 |
+
if (!piece) return false;
|
| 62 |
+
if (this.turn === 0) return piece === '⛂' || piece === '⛃';
|
| 63 |
+
return piece === '⛀' || piece === '⛁';
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
isKing(r, c) {
|
| 67 |
+
return this.board[r][c] === '⛁' || this.board[r][c] === '⛃';
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
makeMove(userId, fromStr, toStr) {
|
| 71 |
+
if (this.status !== 'playing') return { error: "Game not active" };
|
| 72 |
+
if (this.players[this.turn] !== userId) return { error: "Not your turn!" };
|
| 73 |
+
|
| 74 |
+
const from = this.parsePos(fromStr);
|
| 75 |
+
const to = this.parsePos(toStr);
|
| 76 |
+
if (!from || !to) return { error: "Invalid position. Use like: a3 b4" };
|
| 77 |
+
if (!this.isOwn(from.r, from.c)) return { error: "No piece of yours there!" };
|
| 78 |
+
if (this.board[to.r][to.c] !== null) return { error: "Target square occupied!" };
|
| 79 |
+
if ((to.r + to.c) % 2 === 0) return { error: "Can only move to dark squares!" };
|
| 80 |
+
|
| 81 |
+
const dr = to.r - from.r;
|
| 82 |
+
const dc = to.c - from.c;
|
| 83 |
+
const king = this.isKing(from.r, from.c);
|
| 84 |
+
|
| 85 |
+
// Normal move (1 diagonal)
|
| 86 |
+
if (Math.abs(dr) === 1 && Math.abs(dc) === 1) {
|
| 87 |
+
if (!king && this.turn === 0 && dr > 0) return { error: "White men move up only!" };
|
| 88 |
+
if (!king && this.turn === 1 && dr < 0) return { error: "Black men move down only!" };
|
| 89 |
+
this.board[to.r][to.c] = this.board[from.r][from.c];
|
| 90 |
+
this.board[from.r][from.c] = null;
|
| 91 |
+
this.promote(to.r, to.c);
|
| 92 |
+
this.turn = 1 - this.turn;
|
| 93 |
+
return { ok: true, capture: false };
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// Jump (capture, 2 diagonal)
|
| 97 |
+
if (Math.abs(dr) === 2 && Math.abs(dc) === 2) {
|
| 98 |
+
const midR = from.r + dr / 2;
|
| 99 |
+
const midC = from.c + dc / 2;
|
| 100 |
+
if (!this.isEnemy(midR, midC)) return { error: "Must jump over enemy piece!" };
|
| 101 |
+
if (!king && this.turn === 0 && dr > 0) return { error: "White men jump up only!" };
|
| 102 |
+
if (!king && this.turn === 1 && dr < 0) return { error: "Black men jump down only!" };
|
| 103 |
+
this.board[to.r][to.c] = this.board[from.r][from.c];
|
| 104 |
+
this.board[from.r][from.c] = null;
|
| 105 |
+
this.board[midR][midC] = null; // captured
|
| 106 |
+
this.promote(to.r, to.c);
|
| 107 |
+
this.turn = 1 - this.turn;
|
| 108 |
+
return { ok: true, capture: true };
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Flying king: can move/jump multiple squares diagonally
|
| 112 |
+
if (king && Math.abs(dr) === Math.abs(dc)) {
|
| 113 |
+
const stepR = dr > 0 ? 1 : -1;
|
| 114 |
+
const stepC = dc > 0 ? 1 : -1;
|
| 115 |
+
let captured = null;
|
| 116 |
+
let cr = from.r + stepR, cc = from.c + stepC;
|
| 117 |
+
while (cr !== to.r || cc !== to.c) {
|
| 118 |
+
if (this.isOwn(cr, cc)) return { error: "Own piece in the way!" };
|
| 119 |
+
if (this.isEnemy(cr, cc)) {
|
| 120 |
+
if (captured) return { error: "Can't jump over two pieces!" };
|
| 121 |
+
captured = { r: cr, c: cc };
|
| 122 |
+
}
|
| 123 |
+
cr += stepR; cc += stepC;
|
| 124 |
+
}
|
| 125 |
+
this.board[to.r][to.c] = this.board[from.r][from.c];
|
| 126 |
+
this.board[from.r][from.c] = null;
|
| 127 |
+
if (captured) this.board[captured.r][captured.c] = null;
|
| 128 |
+
this.turn = 1 - this.turn;
|
| 129 |
+
return { ok: true, capture: !!captured };
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
return { error: "Invalid move!" };
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
promote(r, c) {
|
| 136 |
+
if (this.board[r][c] === '⛀' && r === 0) this.board[r][c] = '⛁'; // white king
|
| 137 |
+
if (this.board[r][c] === '⛂' && r === 7) this.board[r][c] = '⛃'; // black king
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
checkWinner() {
|
| 141 |
+
let white = 0, black = 0;
|
| 142 |
+
for (let r = 0; r < 8; r++)
|
| 143 |
+
for (let c = 0; c < 8; c++) {
|
| 144 |
+
if (this.board[r][c] === '⛀' || this.board[r][c] === '⛁') white++;
|
| 145 |
+
if (this.board[r][c] === '⛂' || this.board[r][c] === '⛃') black++;
|
| 146 |
+
}
|
| 147 |
+
if (white === 0) return this.players[1]; // black wins
|
| 148 |
+
if (black === 0) return this.players[0]; // white wins
|
| 149 |
+
return null;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
renderBoard() {
|
| 153 |
+
let s = " a b c d e f g h\n";
|
| 154 |
+
for (let r = 0; r < 8; r++) {
|
| 155 |
+
s += (8 - r) + " ";
|
| 156 |
+
for (let c = 0; c < 8; c++) {
|
| 157 |
+
const p = this.board[r][c];
|
| 158 |
+
if (p) s += " " + p;
|
| 159 |
+
else s += ((r + c) % 2 === 1 ? " ·" : " ▪");
|
| 160 |
+
}
|
| 161 |
+
s += "\n";
|
| 162 |
+
}
|
| 163 |
+
return s;
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
module.exports = Checkers;
|
games/Chess.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Chess — Simplified for Telegram text-based play
|
| 3 |
+
* White: ♔♕♖♗♘♙ | Black: ♚♛♜♝♞♟
|
| 4 |
+
* Moves via text: /m e2 e4
|
| 5 |
+
* No castling, en passant, or promotion choice (auto-queen) for simplicity.
|
| 6 |
+
*/
|
| 7 |
+
class Chess {
|
| 8 |
+
constructor(player1, bet) {
|
| 9 |
+
this.players = [player1]; // [white, black]
|
| 10 |
+
this.bet = bet;
|
| 11 |
+
this.board = this.initBoard();
|
| 12 |
+
this.turn = 0;
|
| 13 |
+
this.status = 'lobby';
|
| 14 |
+
this.moveCount = 0;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
initBoard() {
|
| 18 |
+
return [
|
| 19 |
+
['♜','♞','♝','♛','♚','♝','♞','♜'],
|
| 20 |
+
['♟','♟','♟','♟','♟','♟','♟','♟'],
|
| 21 |
+
[null,null,null,null,null,null,null,null],
|
| 22 |
+
[null,null,null,null,null,null,null,null],
|
| 23 |
+
[null,null,null,null,null,null,null,null],
|
| 24 |
+
[null,null,null,null,null,null,null,null],
|
| 25 |
+
['♙','♙','♙','♙','♙','♙','♙','♙'],
|
| 26 |
+
['♖','♘','♗','♕','♔','♗','♘','♖'],
|
| 27 |
+
];
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
addPlayer(userId) {
|
| 31 |
+
if (this.players.length < 2 && !this.players.includes(userId)) {
|
| 32 |
+
this.players.push(userId);
|
| 33 |
+
this.status = 'playing';
|
| 34 |
+
return true;
|
| 35 |
+
}
|
| 36 |
+
return false;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
parsePos(s) {
|
| 40 |
+
if (!s || s.length < 2) return null;
|
| 41 |
+
const c = s.charCodeAt(0) - 97;
|
| 42 |
+
const r = 8 - parseInt(s[1]);
|
| 43 |
+
if (c < 0 || c > 7 || r < 0 || r > 7) return null;
|
| 44 |
+
return { r, c };
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
isWhite(p) { return '♔♕♖♗♘♙'.includes(p); }
|
| 48 |
+
isBlack(p) { return '♚♛♜♝♞♟'.includes(p); }
|
| 49 |
+
isOwn(r, c) {
|
| 50 |
+
const p = this.board[r][c];
|
| 51 |
+
if (!p) return false;
|
| 52 |
+
return this.turn === 0 ? this.isWhite(p) : this.isBlack(p);
|
| 53 |
+
}
|
| 54 |
+
isEnemy(r, c) {
|
| 55 |
+
const p = this.board[r][c];
|
| 56 |
+
if (!p) return false;
|
| 57 |
+
return this.turn === 0 ? this.isBlack(p) : this.isWhite(p);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
makeMove(userId, fromStr, toStr) {
|
| 61 |
+
if (this.status !== 'playing') return { error: "Game not active" };
|
| 62 |
+
if (this.players[this.turn] !== userId) return { error: "Not your turn!" };
|
| 63 |
+
|
| 64 |
+
const from = this.parsePos(fromStr);
|
| 65 |
+
const to = this.parsePos(toStr);
|
| 66 |
+
if (!from || !to) return { error: "Invalid position. Use like: e2 e4" };
|
| 67 |
+
if (!this.isOwn(from.r, from.c)) return { error: "No piece of yours there!" };
|
| 68 |
+
if (this.isOwn(to.r, to.c)) return { error: "Can't capture own piece!" };
|
| 69 |
+
|
| 70 |
+
const piece = this.board[from.r][from.c];
|
| 71 |
+
const valid = this.validateMove(piece, from, to);
|
| 72 |
+
if (!valid) return { error: "Illegal move for that piece!" };
|
| 73 |
+
|
| 74 |
+
const captured = this.board[to.r][to.c];
|
| 75 |
+
this.board[to.r][to.c] = piece;
|
| 76 |
+
this.board[from.r][from.c] = null;
|
| 77 |
+
|
| 78 |
+
// Auto-promote pawns
|
| 79 |
+
if (piece === '♙' && to.r === 0) this.board[to.r][to.c] = '♕';
|
| 80 |
+
if (piece === '♟' && to.r === 7) this.board[to.r][to.c] = '♛';
|
| 81 |
+
|
| 82 |
+
this.turn = 1 - this.turn;
|
| 83 |
+
this.moveCount++;
|
| 84 |
+
return { ok: true, captured };
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
validateMove(piece, from, to) {
|
| 88 |
+
const dr = to.r - from.r, dc = to.c - from.c;
|
| 89 |
+
const ar = Math.abs(dr), ac = Math.abs(dc);
|
| 90 |
+
|
| 91 |
+
// Pawn
|
| 92 |
+
if (piece === '♙') {
|
| 93 |
+
if (dc === 0 && dr === -1 && !this.board[to.r][to.c]) return true;
|
| 94 |
+
if (dc === 0 && dr === -2 && from.r === 6 && !this.board[to.r][to.c] && !this.board[from.r-1][from.c]) return true;
|
| 95 |
+
if (ac === 1 && dr === -1 && this.isEnemy(to.r, to.c)) return true;
|
| 96 |
+
return false;
|
| 97 |
+
}
|
| 98 |
+
if (piece === '♟') {
|
| 99 |
+
if (dc === 0 && dr === 1 && !this.board[to.r][to.c]) return true;
|
| 100 |
+
if (dc === 0 && dr === 2 && from.r === 1 && !this.board[to.r][to.c] && !this.board[from.r+1][from.c]) return true;
|
| 101 |
+
if (ac === 1 && dr === 1 && this.isEnemy(to.r, to.c)) return true;
|
| 102 |
+
return false;
|
| 103 |
+
}
|
| 104 |
+
// Knight
|
| 105 |
+
if (piece === '♘' || piece === '♞') {
|
| 106 |
+
return (ar === 2 && ac === 1) || (ar === 1 && ac === 2);
|
| 107 |
+
}
|
| 108 |
+
// Bishop
|
| 109 |
+
if (piece === '♗' || piece === '♝') {
|
| 110 |
+
return ar === ac && ar > 0 && this.clearDiagonal(from, to);
|
| 111 |
+
}
|
| 112 |
+
// Rook
|
| 113 |
+
if (piece === '♖' || piece === '♜') {
|
| 114 |
+
return (ar === 0 || ac === 0) && (ar + ac > 0) && this.clearStraight(from, to);
|
| 115 |
+
}
|
| 116 |
+
// Queen
|
| 117 |
+
if (piece === '♕' || piece === '♛') {
|
| 118 |
+
if (ar === ac && ar > 0) return this.clearDiagonal(from, to);
|
| 119 |
+
if ((ar === 0 || ac === 0) && ar + ac > 0) return this.clearStraight(from, to);
|
| 120 |
+
return false;
|
| 121 |
+
}
|
| 122 |
+
// King
|
| 123 |
+
if (piece === '♔' || piece === '♚') {
|
| 124 |
+
return ar <= 1 && ac <= 1 && (ar + ac > 0);
|
| 125 |
+
}
|
| 126 |
+
return false;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
clearStraight(from, to) {
|
| 130 |
+
const sr = Math.sign(to.r - from.r), sc = Math.sign(to.c - from.c);
|
| 131 |
+
let r = from.r + sr, c = from.c + sc;
|
| 132 |
+
while (r !== to.r || c !== to.c) {
|
| 133 |
+
if (this.board[r][c]) return false;
|
| 134 |
+
r += sr; c += sc;
|
| 135 |
+
}
|
| 136 |
+
return true;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
clearDiagonal(from, to) {
|
| 140 |
+
const sr = Math.sign(to.r - from.r), sc = Math.sign(to.c - from.c);
|
| 141 |
+
let r = from.r + sr, c = from.c + sc;
|
| 142 |
+
while (r !== to.r || c !== to.c) {
|
| 143 |
+
if (this.board[r][c]) return false;
|
| 144 |
+
r += sr; c += sc;
|
| 145 |
+
}
|
| 146 |
+
return true;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
checkWinner() {
|
| 150 |
+
let whiteKing = false, blackKing = false;
|
| 151 |
+
for (let r = 0; r < 8; r++)
|
| 152 |
+
for (let c = 0; c < 8; c++) {
|
| 153 |
+
if (this.board[r][c] === '♔') whiteKing = true;
|
| 154 |
+
if (this.board[r][c] === '♚') blackKing = true;
|
| 155 |
+
}
|
| 156 |
+
if (!whiteKing) return this.players[1];
|
| 157 |
+
if (!blackKing) return this.players[0];
|
| 158 |
+
if (this.moveCount >= 200) return 'draw'; // force draw after 200 moves
|
| 159 |
+
return null;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
renderBoard() {
|
| 163 |
+
let s = " a b c d e f g h\n";
|
| 164 |
+
for (let r = 0; r < 8; r++) {
|
| 165 |
+
s += (8 - r) + " ";
|
| 166 |
+
for (let c = 0; c < 8; c++) {
|
| 167 |
+
const p = this.board[r][c];
|
| 168 |
+
if (p) s += " " + p + " ";
|
| 169 |
+
else s += ((r + c) % 2 === 0 ? " · " : " ▪ ");
|
| 170 |
+
}
|
| 171 |
+
s += " " + (8 - r) + "\n";
|
| 172 |
+
}
|
| 173 |
+
s += " a b c d e f g h";
|
| 174 |
+
return s;
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
module.exports = Chess;
|
handlers/blackjack.js
CHANGED
|
@@ -3,62 +3,49 @@ const User = require('../models/User');
|
|
| 3 |
const Blackjack = require('../games/Blackjack');
|
| 4 |
const sessions = require('../games/sessions');
|
| 5 |
const leveling = require('../utils/leveling');
|
|
|
|
| 6 |
|
| 7 |
const startBJ = async (client, event) => {
|
| 8 |
const bet = parseInt(event.message.message.split(" ")[1]) || 100;
|
| 9 |
const userId = event.message.senderId.toString();
|
| 10 |
-
let user = await User.findOne({ userId });
|
| 11 |
-
if (!user) user = await User.create({ userId });
|
| 12 |
-
|
| 13 |
if (user.wallet < bet) return event.message.respond({ message: "Insufficient funds!" });
|
| 14 |
|
| 15 |
const gameId = `bj:${userId}:${Date.now()}`;
|
| 16 |
const game = new Blackjack(userId, bet);
|
| 17 |
sessions.set(gameId, game);
|
| 18 |
-
|
| 19 |
-
await updateBJBoard(client, event.chatId, null, game, gameId);
|
| 20 |
};
|
| 21 |
|
| 22 |
-
function
|
| 23 |
return [[
|
| 24 |
new Api.KeyboardButtonCallback({ text: "Hit ➕", data: Buffer.from(`bjhit|${gameId}`) }),
|
| 25 |
new Api.KeyboardButtonCallback({ text: "Stand ✋", data: Buffer.from(`bjstd|${gameId}`) })
|
| 26 |
]];
|
| 27 |
}
|
| 28 |
|
| 29 |
-
|
| 30 |
-
const
|
| 31 |
-
?
|
| 32 |
-
: game.players['dealer'].hand.
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
let message = `🃏 **BLACKJACK**\nBet: $${game.bet}\n\n`;
|
| 37 |
-
message += `🏦 **Dealer:** ${dealerHand} (Score: ${game.status === 'playing' ? '?' : game.players['dealer'].score})\n`;
|
| 38 |
-
message += `👤 **You:** ${playerHand} (Score: ${game.players[game.creatorId].score})`;
|
| 39 |
-
|
| 40 |
-
let buttons = [];
|
| 41 |
-
if (game.status === 'playing') {
|
| 42 |
-
buttons = buildBJButtons(gameId);
|
| 43 |
-
}
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
}
|
| 50 |
}
|
| 51 |
|
| 52 |
const handleBJCallback = async (client, update) => {
|
| 53 |
const data = update.data.toString();
|
| 54 |
if (!data.startsWith("bjhit|") && !data.startsWith("bjstd|")) return;
|
| 55 |
-
|
| 56 |
const userId = update.userId.toString();
|
| 57 |
-
const
|
| 58 |
-
const
|
| 59 |
-
const gameId = parts[1];
|
| 60 |
const game = sessions.get(gameId);
|
| 61 |
-
|
| 62 |
if (!game || game.creatorId !== userId || game.status !== 'playing') return;
|
| 63 |
|
| 64 |
if (action === 'bjhit') {
|
|
@@ -67,55 +54,31 @@ const handleBJCallback = async (client, update) => {
|
|
| 67 |
game.status = 'bust';
|
| 68 |
await finalizeBJ(client, update.peer, update.msgId, game, gameId, 'bust');
|
| 69 |
} else {
|
| 70 |
-
await
|
| 71 |
}
|
| 72 |
} else if (action === 'bjstd') {
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
let result = '';
|
| 79 |
-
const pScore = game.players[userId].score;
|
| 80 |
-
const dScore = game.players['dealer'].score;
|
| 81 |
-
|
| 82 |
-
if (dScore > 21 || pScore > dScore) result = 'win';
|
| 83 |
-
else if (dScore > pScore) result = 'lose';
|
| 84 |
-
else result = 'push';
|
| 85 |
-
|
| 86 |
game.status = 'finished';
|
| 87 |
await finalizeBJ(client, update.peer, update.msgId, game, gameId, result);
|
| 88 |
}
|
| 89 |
};
|
| 90 |
|
| 91 |
async function finalizeBJ(client, peer, msgId, game, gameId, result) {
|
| 92 |
-
let user = await User.findOne({ userId: game.creatorId });
|
| 93 |
-
|
| 94 |
-
let finalMsg = "";
|
| 95 |
-
|
| 96 |
if (result === 'win') {
|
| 97 |
user.wallet += game.bet;
|
| 98 |
-
const
|
| 99 |
-
|
| 100 |
-
if (
|
| 101 |
} else if (result === 'lose' || result === 'bust') {
|
| 102 |
user.wallet -= game.bet;
|
| 103 |
-
|
| 104 |
-
} else {
|
| 105 |
-
finalMsg = "🤝 **PUSH!** It's a draw, money returned.";
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
await user.save();
|
| 109 |
-
|
| 110 |
-
const dealerHand = game.players['dealer'].hand.map(c => `${c.v}${c.s}`).join(" ");
|
| 111 |
-
const playerHand = game.players[game.creatorId].hand.map(c => `${c.v}${c.s}`).join(" ");
|
| 112 |
-
|
| 113 |
-
let message = `🃏 **BLACKJACK - ${result.toUpperCase()}**\nBet: $${game.bet}\n\n`;
|
| 114 |
-
message += `🏦 **Dealer:** ${dealerHand} (Score: ${game.players['dealer'].score})\n`;
|
| 115 |
-
message += `👤 **You:** ${playerHand} (Score: ${game.players[game.creatorId].score})\n\n`;
|
| 116 |
-
message += finalMsg;
|
| 117 |
-
|
| 118 |
-
await client.editMessage(peer, { message: msgId, text: message, buttons: new Api.ReplyInlineMarkup({ rows: [] }) });
|
| 119 |
sessions.delete(gameId);
|
| 120 |
}
|
| 121 |
|
|
|
|
| 3 |
const Blackjack = require('../games/Blackjack');
|
| 4 |
const sessions = require('../games/sessions');
|
| 5 |
const leveling = require('../utils/leveling');
|
| 6 |
+
const { editMsg } = require('../utils/editMsg');
|
| 7 |
|
| 8 |
const startBJ = async (client, event) => {
|
| 9 |
const bet = parseInt(event.message.message.split(" ")[1]) || 100;
|
| 10 |
const userId = event.message.senderId.toString();
|
| 11 |
+
let user = await User.findOne({ userId }) || await User.create({ userId });
|
|
|
|
|
|
|
| 12 |
if (user.wallet < bet) return event.message.respond({ message: "Insufficient funds!" });
|
| 13 |
|
| 14 |
const gameId = `bj:${userId}:${Date.now()}`;
|
| 15 |
const game = new Blackjack(userId, bet);
|
| 16 |
sessions.set(gameId, game);
|
| 17 |
+
await sendBJBoard(client, event.chatId, game, gameId);
|
|
|
|
| 18 |
};
|
| 19 |
|
| 20 |
+
function bjButtons(gameId) {
|
| 21 |
return [[
|
| 22 |
new Api.KeyboardButtonCallback({ text: "Hit ➕", data: Buffer.from(`bjhit|${gameId}`) }),
|
| 23 |
new Api.KeyboardButtonCallback({ text: "Stand ✋", data: Buffer.from(`bjstd|${gameId}`) })
|
| 24 |
]];
|
| 25 |
}
|
| 26 |
|
| 27 |
+
function bjText(game, showDealer) {
|
| 28 |
+
const dHand = showDealer
|
| 29 |
+
? game.players['dealer'].hand.map(c => `${c.v}${c.s}`).join(" ")
|
| 30 |
+
: `${game.players['dealer'].hand[0].v}${game.players['dealer'].hand[0].s} ❓`;
|
| 31 |
+
const pHand = game.players[game.creatorId].hand.map(c => `${c.v}${c.s}`).join(" ");
|
| 32 |
+
return `🃏 **BLACKJACK** | Bet: $${game.bet}\n\n🏦 Dealer: ${dHand} (${showDealer ? game.players['dealer'].score : '?'})\n👤 You: ${pHand} (${game.players[game.creatorId].score})`;
|
| 33 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
async function sendBJBoard(client, peer, game, gameId) {
|
| 36 |
+
await client.sendMessage(peer, {
|
| 37 |
+
message: bjText(game, false),
|
| 38 |
+
buttons: client.buildReplyMarkup(bjButtons(gameId))
|
| 39 |
+
});
|
| 40 |
}
|
| 41 |
|
| 42 |
const handleBJCallback = async (client, update) => {
|
| 43 |
const data = update.data.toString();
|
| 44 |
if (!data.startsWith("bjhit|") && !data.startsWith("bjstd|")) return;
|
|
|
|
| 45 |
const userId = update.userId.toString();
|
| 46 |
+
const action = data.split("|")[0];
|
| 47 |
+
const gameId = data.split("|")[1];
|
|
|
|
| 48 |
const game = sessions.get(gameId);
|
|
|
|
| 49 |
if (!game || game.creatorId !== userId || game.status !== 'playing') return;
|
| 50 |
|
| 51 |
if (action === 'bjhit') {
|
|
|
|
| 54 |
game.status = 'bust';
|
| 55 |
await finalizeBJ(client, update.peer, update.msgId, game, gameId, 'bust');
|
| 56 |
} else {
|
| 57 |
+
await editMsg(client, update.peer, update.msgId, bjText(game, false), bjButtons(gameId));
|
| 58 |
}
|
| 59 |
} else if (action === 'bjstd') {
|
| 60 |
+
while (game.players['dealer'].score < 17) game.deal('dealer');
|
| 61 |
+
const p = game.players[userId].score, d = game.players['dealer'].score;
|
| 62 |
+
const result = d > 21 || p > d ? 'win' : d > p ? 'lose' : 'push';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
game.status = 'finished';
|
| 64 |
await finalizeBJ(client, update.peer, update.msgId, game, gameId, result);
|
| 65 |
}
|
| 66 |
};
|
| 67 |
|
| 68 |
async function finalizeBJ(client, peer, msgId, game, gameId, result) {
|
| 69 |
+
let user = await User.findOne({ userId: game.creatorId }) || await User.create({ userId: game.creatorId });
|
| 70 |
+
let extra = "";
|
|
|
|
|
|
|
| 71 |
if (result === 'win') {
|
| 72 |
user.wallet += game.bet;
|
| 73 |
+
const xp = await leveling.addXP(game.creatorId, 50);
|
| 74 |
+
extra = `\n\n🎉 **YOU WIN!** +$${game.bet} +50XP`;
|
| 75 |
+
if (xp.leveledUp) extra += ` 🆙 Lv${xp.level}!`;
|
| 76 |
} else if (result === 'lose' || result === 'bust') {
|
| 77 |
user.wallet -= game.bet;
|
| 78 |
+
extra = result === 'bust' ? `\n\n💥 **BUST!** Lost $${game.bet}` : `\n\n💀 **DEALER WINS!** Lost $${game.bet}`;
|
| 79 |
+
} else { extra = "\n\n🤝 **PUSH!** Draw."; }
|
|
|
|
|
|
|
|
|
|
| 80 |
await user.save();
|
| 81 |
+
await editMsg(client, peer, msgId, bjText(game, true) + extra, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
sessions.delete(gameId);
|
| 83 |
}
|
| 84 |
|
handlers/boardHandler.js
CHANGED
|
@@ -2,150 +2,92 @@ const { Api } = require("telegram");
|
|
| 2 |
const User = require('../models/User');
|
| 3 |
const sessions = require('../games/sessions');
|
| 4 |
const leveling = require('../utils/leveling');
|
|
|
|
| 5 |
|
| 6 |
const handleBoardCallback = async (client, update) => {
|
| 7 |
const data = update.data.toString();
|
| 8 |
const userId = update.userId.toString();
|
| 9 |
|
| 10 |
-
// ── XOX Join: xjoin|gameId ──
|
| 11 |
if (data.startsWith("xjoin|")) {
|
| 12 |
const gameId = data.replace("xjoin|", "");
|
| 13 |
const g = sessions.get(gameId);
|
| 14 |
if (!g || g.status !== 'lobby' || g.players.includes(userId)) return;
|
| 15 |
-
|
| 16 |
-
let user = await User.findOne({ userId });
|
| 17 |
-
if (!user) user = await User.create({ userId });
|
| 18 |
if (user.wallet < g.bet) return;
|
| 19 |
-
|
| 20 |
g.addPlayer(userId);
|
| 21 |
await updateBoard(client, update.peer, update.msgId, g, gameId, 'xox');
|
| 22 |
return;
|
| 23 |
}
|
| 24 |
-
|
| 25 |
-
// ── C4 Join: cjoin|gameId ──
|
| 26 |
if (data.startsWith("cjoin|")) {
|
| 27 |
const gameId = data.replace("cjoin|", "");
|
| 28 |
const g = sessions.get(gameId);
|
| 29 |
if (!g || g.status !== 'lobby' || g.players.includes(userId)) return;
|
| 30 |
-
|
| 31 |
-
let user = await User.findOne({ userId });
|
| 32 |
-
if (!user) user = await User.create({ userId });
|
| 33 |
if (user.wallet < g.bet) return;
|
| 34 |
-
|
| 35 |
g.addPlayer(userId);
|
| 36 |
await updateBoard(client, update.peer, update.msgId, g, gameId, 'c4');
|
| 37 |
return;
|
| 38 |
}
|
| 39 |
-
|
| 40 |
-
// ── XOX Move: xmove|index|gameId ──
|
| 41 |
if (data.startsWith("xmove|")) {
|
| 42 |
const parts = data.split("|");
|
| 43 |
-
const index = parseInt(parts[1]);
|
| 44 |
-
const
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
if (
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
const winner = xoxGame.checkWinner();
|
| 51 |
-
if (winner) {
|
| 52 |
-
await handleEndGame(client, update.peer, update.msgId, xoxGame, winner, gameId);
|
| 53 |
-
} else {
|
| 54 |
-
await updateBoard(client, update.peer, update.msgId, xoxGame, gameId, 'xox');
|
| 55 |
-
}
|
| 56 |
return;
|
| 57 |
}
|
| 58 |
-
|
| 59 |
-
// ── C4 Move: cmove|col|gameId ──
|
| 60 |
if (data.startsWith("cmove|")) {
|
| 61 |
const parts = data.split("|");
|
| 62 |
-
const col = parseInt(parts[1]);
|
| 63 |
-
const
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
if (
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
const winner = c4Game.checkWinner();
|
| 70 |
-
if (winner) {
|
| 71 |
-
await handleEndGame(client, update.peer, update.msgId, c4Game, winner, gameId);
|
| 72 |
-
} else {
|
| 73 |
-
await updateBoard(client, update.peer, update.msgId, c4Game, gameId, 'c4');
|
| 74 |
-
}
|
| 75 |
return;
|
| 76 |
}
|
| 77 |
};
|
| 78 |
|
| 79 |
async function updateBoard(client, peer, msgId, game, gameId, type) {
|
| 80 |
-
let buttons = [];
|
| 81 |
-
let message = "";
|
| 82 |
-
|
| 83 |
if (type === 'xox') {
|
| 84 |
-
message = `❌⭕ **TIC-TAC-TOE**\nBet: $${game.bet}\
|
| 85 |
for (let i = 0; i < 3; i++) {
|
| 86 |
let row = [];
|
| 87 |
for (let j = 0; j < 3; j++) {
|
| 88 |
const idx = i * 3 + j;
|
| 89 |
-
row.push(new Api.KeyboardButtonCallback({
|
| 90 |
-
text: game.board[idx] || '⬜',
|
| 91 |
-
data: Buffer.from(`xmove|${idx}|${gameId}`)
|
| 92 |
-
}));
|
| 93 |
}
|
| 94 |
buttons.push(row);
|
| 95 |
}
|
| 96 |
} else if (type === 'c4') {
|
| 97 |
-
message = `🔴🟡 **CONNECT FOUR**\nBet: $${game.bet}\
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
for (let r = 0; r < game.rows; r++) {
|
| 101 |
-
boardText += game.board[r].map(cell => cell || '⚪').join("") + "\n";
|
| 102 |
-
}
|
| 103 |
-
message += "\n" + boardText;
|
| 104 |
-
|
| 105 |
let row = [];
|
| 106 |
-
for (let c = 0; c < game.cols; c++)
|
| 107 |
-
row.push(new Api.KeyboardButtonCallback({
|
| 108 |
-
text: `${c + 1}`,
|
| 109 |
-
data: Buffer.from(`cmove|${c}|${gameId}`)
|
| 110 |
-
}));
|
| 111 |
-
}
|
| 112 |
buttons.push(row);
|
| 113 |
}
|
| 114 |
-
|
| 115 |
-
await client.editMessage(peer, {
|
| 116 |
-
message: msgId,
|
| 117 |
-
text: message,
|
| 118 |
-
buttons: client.buildReplyMarkup(buttons)
|
| 119 |
-
});
|
| 120 |
}
|
| 121 |
|
| 122 |
async function handleEndGame(client, peer, msgId, game, winner, gameId) {
|
| 123 |
let msg = "";
|
| 124 |
-
if (winner === 'draw') {
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
let winUser = await User.findOne({ userId: winner });
|
| 128 |
-
if (!winUser) winUser = await User.create({ userId: winner });
|
| 129 |
const loserId = game.players.find(id => id !== winner);
|
| 130 |
-
let loseUser = await User.findOne({ userId: loserId });
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
winUser.wallet += game.bet;
|
| 134 |
-
loseUser.wallet -= game.bet;
|
| 135 |
-
|
| 136 |
const xpRes = await leveling.addXP(winner, 100);
|
| 137 |
-
msg = `🏆 **WINNER!**
|
| 138 |
-
if (xpRes.leveledUp) msg += `\n🆙
|
| 139 |
-
|
| 140 |
-
await winUser.save();
|
| 141 |
-
await loseUser.save();
|
| 142 |
}
|
| 143 |
-
|
| 144 |
-
await client.editMessage(peer, {
|
| 145 |
-
message: msgId,
|
| 146 |
-
text: msg,
|
| 147 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 148 |
-
});
|
| 149 |
sessions.delete(gameId);
|
| 150 |
}
|
| 151 |
|
|
|
|
| 2 |
const User = require('../models/User');
|
| 3 |
const sessions = require('../games/sessions');
|
| 4 |
const leveling = require('../utils/leveling');
|
| 5 |
+
const { editMsg } = require('../utils/editMsg');
|
| 6 |
|
| 7 |
const handleBoardCallback = async (client, update) => {
|
| 8 |
const data = update.data.toString();
|
| 9 |
const userId = update.userId.toString();
|
| 10 |
|
|
|
|
| 11 |
if (data.startsWith("xjoin|")) {
|
| 12 |
const gameId = data.replace("xjoin|", "");
|
| 13 |
const g = sessions.get(gameId);
|
| 14 |
if (!g || g.status !== 'lobby' || g.players.includes(userId)) return;
|
| 15 |
+
let user = await User.findOne({ userId }) || await User.create({ userId });
|
|
|
|
|
|
|
| 16 |
if (user.wallet < g.bet) return;
|
|
|
|
| 17 |
g.addPlayer(userId);
|
| 18 |
await updateBoard(client, update.peer, update.msgId, g, gameId, 'xox');
|
| 19 |
return;
|
| 20 |
}
|
|
|
|
|
|
|
| 21 |
if (data.startsWith("cjoin|")) {
|
| 22 |
const gameId = data.replace("cjoin|", "");
|
| 23 |
const g = sessions.get(gameId);
|
| 24 |
if (!g || g.status !== 'lobby' || g.players.includes(userId)) return;
|
| 25 |
+
let user = await User.findOne({ userId }) || await User.create({ userId });
|
|
|
|
|
|
|
| 26 |
if (user.wallet < g.bet) return;
|
|
|
|
| 27 |
g.addPlayer(userId);
|
| 28 |
await updateBoard(client, update.peer, update.msgId, g, gameId, 'c4');
|
| 29 |
return;
|
| 30 |
}
|
|
|
|
|
|
|
| 31 |
if (data.startsWith("xmove|")) {
|
| 32 |
const parts = data.split("|");
|
| 33 |
+
const index = parseInt(parts[1]), gameId = parts[2];
|
| 34 |
+
const game = sessions.get(gameId);
|
| 35 |
+
if (!game || game.status !== 'playing' || !game.makeMove(userId, index)) return;
|
| 36 |
+
const winner = game.checkWinner();
|
| 37 |
+
if (winner) await handleEndGame(client, update.peer, update.msgId, game, winner, gameId);
|
| 38 |
+
else await updateBoard(client, update.peer, update.msgId, game, gameId, 'xox');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
return;
|
| 40 |
}
|
|
|
|
|
|
|
| 41 |
if (data.startsWith("cmove|")) {
|
| 42 |
const parts = data.split("|");
|
| 43 |
+
const col = parseInt(parts[1]), gameId = parts[2];
|
| 44 |
+
const game = sessions.get(gameId);
|
| 45 |
+
if (!game || game.status !== 'playing' || !game.makeMove(userId, col)) return;
|
| 46 |
+
const winner = game.checkWinner();
|
| 47 |
+
if (winner) await handleEndGame(client, update.peer, update.msgId, game, winner, gameId);
|
| 48 |
+
else await updateBoard(client, update.peer, update.msgId, game, gameId, 'c4');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
return;
|
| 50 |
}
|
| 51 |
};
|
| 52 |
|
| 53 |
async function updateBoard(client, peer, msgId, game, gameId, type) {
|
| 54 |
+
let buttons = [], message = "";
|
|
|
|
|
|
|
| 55 |
if (type === 'xox') {
|
| 56 |
+
message = `❌⭕ **TIC-TAC-TOE**\nBet: $${game.bet}\nTurn: Player ${game.turn + 1}`;
|
| 57 |
for (let i = 0; i < 3; i++) {
|
| 58 |
let row = [];
|
| 59 |
for (let j = 0; j < 3; j++) {
|
| 60 |
const idx = i * 3 + j;
|
| 61 |
+
row.push(new Api.KeyboardButtonCallback({ text: game.board[idx] || '⬜', data: Buffer.from(`xmove|${idx}|${gameId}`) }));
|
|
|
|
|
|
|
|
|
|
| 62 |
}
|
| 63 |
buttons.push(row);
|
| 64 |
}
|
| 65 |
} else if (type === 'c4') {
|
| 66 |
+
message = `🔴🟡 **CONNECT FOUR**\nBet: $${game.bet}\nTurn: Player ${game.turn + 1}\n`;
|
| 67 |
+
for (let r = 0; r < game.rows; r++)
|
| 68 |
+
message += game.board[r].map(c => c || '⚪').join("") + "\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
let row = [];
|
| 70 |
+
for (let c = 0; c < game.cols; c++)
|
| 71 |
+
row.push(new Api.KeyboardButtonCallback({ text: `${c+1}`, data: Buffer.from(`cmove|${c}|${gameId}`) }));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
buttons.push(row);
|
| 73 |
}
|
| 74 |
+
await editMsg(client, peer, msgId, message, buttons);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
}
|
| 76 |
|
| 77 |
async function handleEndGame(client, peer, msgId, game, winner, gameId) {
|
| 78 |
let msg = "";
|
| 79 |
+
if (winner === 'draw') { msg = "🤝 **DRAW!** Money returned."; }
|
| 80 |
+
else {
|
| 81 |
+
let winUser = await User.findOne({ userId: winner }) || await User.create({ userId: winner });
|
|
|
|
|
|
|
| 82 |
const loserId = game.players.find(id => id !== winner);
|
| 83 |
+
let loseUser = await User.findOne({ userId: loserId }) || await User.create({ userId: loserId });
|
| 84 |
+
winUser.wallet += game.bet; loseUser.wallet -= game.bet;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
const xpRes = await leveling.addXP(winner, 100);
|
| 86 |
+
msg = `🏆 **WINNER!** Prize: $${game.bet}`;
|
| 87 |
+
if (xpRes.leveledUp) msg += `\n🆙 Level ${xpRes.level}!`;
|
| 88 |
+
await winUser.save(); await loseUser.save();
|
|
|
|
|
|
|
| 89 |
}
|
| 90 |
+
await editMsg(client, peer, msgId, msg, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
sessions.delete(gameId);
|
| 92 |
}
|
| 93 |
|
handlers/combat.js
CHANGED
|
@@ -30,6 +30,8 @@ const kill = async (client, event) => {
|
|
| 30 |
// Reward for killing
|
| 31 |
const reward = Math.floor(Math.random() * 500) + 100;
|
| 32 |
killer.wallet += reward;
|
|
|
|
|
|
|
| 33 |
victim.isDead = true;
|
| 34 |
victim.lastDeath = new Date();
|
| 35 |
victim.health = 0;
|
|
@@ -84,6 +86,8 @@ const rob = async (client, event) => {
|
|
| 84 |
if (success) {
|
| 85 |
const stolen = Math.floor(Math.random() * (victim.wallet * 0.7)) + 1;
|
| 86 |
robber.wallet += stolen;
|
|
|
|
|
|
|
| 87 |
victim.wallet -= stolen;
|
| 88 |
|
| 89 |
const xpRes = await leveling.addXP(robberId, 50);
|
|
|
|
| 30 |
// Reward for killing
|
| 31 |
const reward = Math.floor(Math.random() * 500) + 100;
|
| 32 |
killer.wallet += reward;
|
| 33 |
+
killer.kills = (killer.kills || 0) + 1;
|
| 34 |
+
killer.totalEarned = (killer.totalEarned || 0) + reward;
|
| 35 |
victim.isDead = true;
|
| 36 |
victim.lastDeath = new Date();
|
| 37 |
victim.health = 0;
|
|
|
|
| 86 |
if (success) {
|
| 87 |
const stolen = Math.floor(Math.random() * (victim.wallet * 0.7)) + 1;
|
| 88 |
robber.wallet += stolen;
|
| 89 |
+
robber.robs = (robber.robs || 0) + 1;
|
| 90 |
+
robber.totalEarned = (robber.totalEarned || 0) + stolen;
|
| 91 |
victim.wallet -= stolen;
|
| 92 |
|
| 93 |
const xpRes = await leveling.addXP(robberId, 50);
|
handlers/games.js
CHANGED
|
@@ -2,6 +2,7 @@ const { Api } = require("telegram");
|
|
| 2 |
const User = require('../models/User');
|
| 3 |
const HackGame = require('../games/HackGame');
|
| 4 |
const sessions = require('../games/sessions');
|
|
|
|
| 5 |
|
| 6 |
const startHack = async (client, event) => {
|
| 7 |
const parts = event.message.message.split(" ");
|
|
@@ -19,39 +20,19 @@ const startHack = async (client, event) => {
|
|
| 19 |
const game = new HackGame(length, bet);
|
| 20 |
sessions.set(gameId, { type: 'hack', game, userId });
|
| 21 |
|
| 22 |
-
const buttons = [];
|
| 23 |
-
const digits = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"];
|
| 24 |
-
let row = [];
|
| 25 |
-
for (const d of digits) {
|
| 26 |
-
row.push(new Api.KeyboardButtonCallback({ text: d, data: Buffer.from(`hinp|${gameId}|${d}`) }));
|
| 27 |
-
if (row.length === 3) {
|
| 28 |
-
buttons.push(row);
|
| 29 |
-
row = [];
|
| 30 |
-
}
|
| 31 |
-
}
|
| 32 |
-
if (row.length > 0) buttons.push(row);
|
| 33 |
-
buttons.push([
|
| 34 |
-
new Api.KeyboardButtonCallback({ text: "CLR", data: Buffer.from(`hclr|${gameId}`) }),
|
| 35 |
-
new Api.KeyboardButtonCallback({ text: "ENTER", data: Buffer.from(`hent|${gameId}`) })
|
| 36 |
-
]);
|
| 37 |
-
|
| 38 |
await client.sendMessage(event.chatId, {
|
| 39 |
message: `🔍 **HACK INITIALIZED**\nBet: $${bet}\nLength: ${length}\nGuess the ${length}-digit PIN:\n\nInput: [ ${"_".repeat(length)} ]`,
|
| 40 |
-
buttons: client.buildReplyMarkup(
|
| 41 |
});
|
| 42 |
};
|
| 43 |
|
| 44 |
-
|
| 45 |
-
function buildHackKeyboard(gameId, game) {
|
| 46 |
const buttons = [];
|
| 47 |
const digits = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"];
|
| 48 |
let row = [];
|
| 49 |
for (const d of digits) {
|
| 50 |
row.push(new Api.KeyboardButtonCallback({ text: d, data: Buffer.from(`hinp|${gameId}|${d}`) }));
|
| 51 |
-
if (row.length === 3) {
|
| 52 |
-
buttons.push(row);
|
| 53 |
-
row = [];
|
| 54 |
-
}
|
| 55 |
}
|
| 56 |
if (row.length > 0) buttons.push(row);
|
| 57 |
buttons.push([
|
|
@@ -65,23 +46,19 @@ const handleCallback = async (client, update) => {
|
|
| 65 |
const data = update.data.toString();
|
| 66 |
const userId = update.userId.toString();
|
| 67 |
|
| 68 |
-
// Callback data format: hinp|gameId|digit, hclr|gameId, hent|gameId
|
| 69 |
if (!data.startsWith("hinp|") && !data.startsWith("hclr|") && !data.startsWith("hent|")) return;
|
| 70 |
|
| 71 |
const parts = data.split("|");
|
| 72 |
-
const action = parts[0];
|
| 73 |
const gameId = parts[1];
|
| 74 |
-
const val = parts[2];
|
| 75 |
|
| 76 |
const session = sessions.get(gameId);
|
| 77 |
if (!session || session.userId !== userId) return;
|
| 78 |
-
|
| 79 |
const { game } = session;
|
| 80 |
|
| 81 |
if (action === "hinp") {
|
| 82 |
-
if (game.currentGuess.length < game.length)
|
| 83 |
-
game.currentGuess += val;
|
| 84 |
-
}
|
| 85 |
} else if (action === "hclr") {
|
| 86 |
game.currentGuess = "";
|
| 87 |
} else if (action === "hent") {
|
|
@@ -91,49 +68,32 @@ const handleCallback = async (client, update) => {
|
|
| 91 |
game.attempts++;
|
| 92 |
|
| 93 |
if (result.bulls === game.length) {
|
| 94 |
-
// WIN
|
| 95 |
let user = await User.findOne({ userId });
|
| 96 |
if (!user) user = await User.create({ userId });
|
| 97 |
const prize = game.bet * game.length;
|
| 98 |
user.wallet += prize;
|
| 99 |
await user.save();
|
| 100 |
-
|
| 101 |
-
await client.editMessage(update.peer, {
|
| 102 |
-
message: update.msgId,
|
| 103 |
-
text: `🎯 **ACCESS GRANTED**\n\nPIN: ${game.target}\nReward: $${prize}\nUser: ${userId}`,
|
| 104 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 105 |
-
});
|
| 106 |
sessions.delete(gameId);
|
| 107 |
return;
|
| 108 |
} else if (game.attempts >= game.maxAttempts) {
|
| 109 |
-
// LOSE
|
| 110 |
let user = await User.findOne({ userId });
|
| 111 |
if (!user) user = await User.create({ userId });
|
| 112 |
user.wallet -= game.bet;
|
| 113 |
await user.save();
|
| 114 |
-
|
| 115 |
-
await client.editMessage(update.peer, {
|
| 116 |
-
message: update.msgId,
|
| 117 |
-
text: `❌ **SYSTEM LOCKED**\n\nYou failed to crack the PIN: ${game.target}\nLost: $${game.bet}`,
|
| 118 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 119 |
-
});
|
| 120 |
sessions.delete(gameId);
|
| 121 |
return;
|
| 122 |
} else {
|
| 123 |
-
|
| 124 |
-
const history = `Guess: ${game.currentGuess} -> Bulls: ${result.bulls}, Cows: ${result.cows}`;
|
| 125 |
-
game.lastResult = history;
|
| 126 |
game.currentGuess = "";
|
| 127 |
}
|
| 128 |
}
|
| 129 |
|
| 130 |
const displayGuess = game.currentGuess.padEnd(game.length, "_");
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
text: `🔍 **HACKING...**\nBet: $${game.bet}\nAttempt: ${game.attempts}/${game.maxAttempts}\nLast: ${game.lastResult || "None"}\n\nInput: [ ${displayGuess} ]`,
|
| 135 |
-
buttons: client.buildReplyMarkup(keyboard)
|
| 136 |
-
});
|
| 137 |
};
|
| 138 |
|
| 139 |
const startDice = async (client, event) => {
|
|
@@ -150,7 +110,7 @@ const startDice = async (client, event) => {
|
|
| 150 |
sessions.set(gameId, { type: 'dice', creator: userId, bet, status: 'pending' });
|
| 151 |
|
| 152 |
await client.sendMessage(event.chatId, {
|
| 153 |
-
message: `🎲 **DICE DUEL**\nCreator: ${userId}\nBet: $${bet}\n\nWaiting for
|
| 154 |
buttons: client.buildReplyMarkup([
|
| 155 |
[new Api.KeyboardButtonCallback({ text: "Join Duel", data: Buffer.from(`djoin|${gameId}`) })]
|
| 156 |
])
|
|
@@ -160,12 +120,10 @@ const startDice = async (client, event) => {
|
|
| 160 |
const handleDiceCallback = async (client, update) => {
|
| 161 |
const data = update.data.toString();
|
| 162 |
const userId = update.userId.toString();
|
| 163 |
-
|
| 164 |
if (!data.startsWith("djoin|")) return;
|
| 165 |
|
| 166 |
const gameId = data.replace("djoin|", "");
|
| 167 |
const session = sessions.get(gameId);
|
| 168 |
-
|
| 169 |
if (!session || session.status !== 'pending') return;
|
| 170 |
if (session.creator === userId) return;
|
| 171 |
|
|
@@ -173,40 +131,25 @@ const handleDiceCallback = async (client, update) => {
|
|
| 173 |
if (!opponent) opponent = await User.create({ userId });
|
| 174 |
if (opponent.wallet < session.bet) return;
|
| 175 |
|
| 176 |
-
session.opponent = userId;
|
| 177 |
-
session.status = 'playing';
|
| 178 |
-
|
| 179 |
const creatorRoll = Math.floor(Math.random() * 6) + 1;
|
| 180 |
const opponentRoll = Math.floor(Math.random() * 6) + 1;
|
| 181 |
|
| 182 |
-
let resultMsg = `🎲 **DICE DUEL RESULT**\n\n`;
|
| 183 |
-
resultMsg += `Creator (${session.creator}): ${creatorRoll}\n`;
|
| 184 |
-
resultMsg += `Opponent (${userId}): ${opponentRoll}\n\n`;
|
| 185 |
-
|
| 186 |
let creator = await User.findOne({ userId: session.creator });
|
| 187 |
if (!creator) creator = await User.create({ userId: session.creator });
|
| 188 |
|
|
|
|
| 189 |
if (creatorRoll > opponentRoll) {
|
| 190 |
-
creator.wallet += session.bet;
|
| 191 |
-
|
| 192 |
-
resultMsg += `🏆 **Winner: Creator!** (+$${session.bet})`;
|
| 193 |
} else if (opponentRoll > creatorRoll) {
|
| 194 |
-
creator.wallet -= session.bet;
|
| 195 |
-
|
| 196 |
-
resultMsg += `🏆 **Winner: Opponent!** (+$${session.bet})`;
|
| 197 |
} else {
|
| 198 |
-
resultMsg += `🤝
|
| 199 |
}
|
| 200 |
-
|
| 201 |
-
await creator.save();
|
| 202 |
-
await opponent.save();
|
| 203 |
sessions.delete(gameId);
|
| 204 |
-
|
| 205 |
-
await client.editMessage(update.peer, {
|
| 206 |
-
message: update.msgId,
|
| 207 |
-
text: resultMsg,
|
| 208 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 209 |
-
});
|
| 210 |
};
|
| 211 |
|
| 212 |
module.exports = { startHack, startDice, handleCallback, handleDiceCallback };
|
|
|
|
| 2 |
const User = require('../models/User');
|
| 3 |
const HackGame = require('../games/HackGame');
|
| 4 |
const sessions = require('../games/sessions');
|
| 5 |
+
const { editMsg } = require('../utils/editMsg');
|
| 6 |
|
| 7 |
const startHack = async (client, event) => {
|
| 8 |
const parts = event.message.message.split(" ");
|
|
|
|
| 20 |
const game = new HackGame(length, bet);
|
| 21 |
sessions.set(gameId, { type: 'hack', game, userId });
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
await client.sendMessage(event.chatId, {
|
| 24 |
message: `🔍 **HACK INITIALIZED**\nBet: $${bet}\nLength: ${length}\nGuess the ${length}-digit PIN:\n\nInput: [ ${"_".repeat(length)} ]`,
|
| 25 |
+
buttons: client.buildReplyMarkup(buildHackKeyboard(gameId))
|
| 26 |
});
|
| 27 |
};
|
| 28 |
|
| 29 |
+
function buildHackKeyboard(gameId) {
|
|
|
|
| 30 |
const buttons = [];
|
| 31 |
const digits = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"];
|
| 32 |
let row = [];
|
| 33 |
for (const d of digits) {
|
| 34 |
row.push(new Api.KeyboardButtonCallback({ text: d, data: Buffer.from(`hinp|${gameId}|${d}`) }));
|
| 35 |
+
if (row.length === 3) { buttons.push(row); row = []; }
|
|
|
|
|
|
|
|
|
|
| 36 |
}
|
| 37 |
if (row.length > 0) buttons.push(row);
|
| 38 |
buttons.push([
|
|
|
|
| 46 |
const data = update.data.toString();
|
| 47 |
const userId = update.userId.toString();
|
| 48 |
|
|
|
|
| 49 |
if (!data.startsWith("hinp|") && !data.startsWith("hclr|") && !data.startsWith("hent|")) return;
|
| 50 |
|
| 51 |
const parts = data.split("|");
|
| 52 |
+
const action = parts[0];
|
| 53 |
const gameId = parts[1];
|
| 54 |
+
const val = parts[2];
|
| 55 |
|
| 56 |
const session = sessions.get(gameId);
|
| 57 |
if (!session || session.userId !== userId) return;
|
|
|
|
| 58 |
const { game } = session;
|
| 59 |
|
| 60 |
if (action === "hinp") {
|
| 61 |
+
if (game.currentGuess.length < game.length) game.currentGuess += val;
|
|
|
|
|
|
|
| 62 |
} else if (action === "hclr") {
|
| 63 |
game.currentGuess = "";
|
| 64 |
} else if (action === "hent") {
|
|
|
|
| 68 |
game.attempts++;
|
| 69 |
|
| 70 |
if (result.bulls === game.length) {
|
|
|
|
| 71 |
let user = await User.findOne({ userId });
|
| 72 |
if (!user) user = await User.create({ userId });
|
| 73 |
const prize = game.bet * game.length;
|
| 74 |
user.wallet += prize;
|
| 75 |
await user.save();
|
| 76 |
+
await editMsg(client, update.peer, update.msgId, `🎯 **ACCESS GRANTED**\n\nPIN: ${game.target}\nReward: $${prize}`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
sessions.delete(gameId);
|
| 78 |
return;
|
| 79 |
} else if (game.attempts >= game.maxAttempts) {
|
|
|
|
| 80 |
let user = await User.findOne({ userId });
|
| 81 |
if (!user) user = await User.create({ userId });
|
| 82 |
user.wallet -= game.bet;
|
| 83 |
await user.save();
|
| 84 |
+
await editMsg(client, update.peer, update.msgId, `❌ **SYSTEM LOCKED**\n\nPIN was: ${game.target}\nLost: $${game.bet}`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
sessions.delete(gameId);
|
| 86 |
return;
|
| 87 |
} else {
|
| 88 |
+
game.lastResult = `Guess: ${game.currentGuess} → Bulls: ${result.bulls}, Cows: ${result.cows}`;
|
|
|
|
|
|
|
| 89 |
game.currentGuess = "";
|
| 90 |
}
|
| 91 |
}
|
| 92 |
|
| 93 |
const displayGuess = game.currentGuess.padEnd(game.length, "_");
|
| 94 |
+
await editMsg(client, update.peer, update.msgId,
|
| 95 |
+
`🔍 **HACKING...**\nBet: $${game.bet}\nAttempt: ${game.attempts}/${game.maxAttempts}\nLast: ${game.lastResult || "None"}\n\nInput: [ ${displayGuess} ]`,
|
| 96 |
+
buildHackKeyboard(gameId));
|
|
|
|
|
|
|
|
|
|
| 97 |
};
|
| 98 |
|
| 99 |
const startDice = async (client, event) => {
|
|
|
|
| 110 |
sessions.set(gameId, { type: 'dice', creator: userId, bet, status: 'pending' });
|
| 111 |
|
| 112 |
await client.sendMessage(event.chatId, {
|
| 113 |
+
message: `🎲 **DICE DUEL**\nCreator: ${userId}\nBet: $${bet}\n\nWaiting for opponent...`,
|
| 114 |
buttons: client.buildReplyMarkup([
|
| 115 |
[new Api.KeyboardButtonCallback({ text: "Join Duel", data: Buffer.from(`djoin|${gameId}`) })]
|
| 116 |
])
|
|
|
|
| 120 |
const handleDiceCallback = async (client, update) => {
|
| 121 |
const data = update.data.toString();
|
| 122 |
const userId = update.userId.toString();
|
|
|
|
| 123 |
if (!data.startsWith("djoin|")) return;
|
| 124 |
|
| 125 |
const gameId = data.replace("djoin|", "");
|
| 126 |
const session = sessions.get(gameId);
|
|
|
|
| 127 |
if (!session || session.status !== 'pending') return;
|
| 128 |
if (session.creator === userId) return;
|
| 129 |
|
|
|
|
| 131 |
if (!opponent) opponent = await User.create({ userId });
|
| 132 |
if (opponent.wallet < session.bet) return;
|
| 133 |
|
|
|
|
|
|
|
|
|
|
| 134 |
const creatorRoll = Math.floor(Math.random() * 6) + 1;
|
| 135 |
const opponentRoll = Math.floor(Math.random() * 6) + 1;
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
let creator = await User.findOne({ userId: session.creator });
|
| 138 |
if (!creator) creator = await User.create({ userId: session.creator });
|
| 139 |
|
| 140 |
+
let resultMsg = `🎲 **DICE DUEL RESULT**\n\nCreator: 🎲 ${creatorRoll}\nOpponent: 🎲 ${opponentRoll}\n\n`;
|
| 141 |
if (creatorRoll > opponentRoll) {
|
| 142 |
+
creator.wallet += session.bet; opponent.wallet -= session.bet;
|
| 143 |
+
resultMsg += `🏆 Creator wins +$${session.bet}`;
|
|
|
|
| 144 |
} else if (opponentRoll > creatorRoll) {
|
| 145 |
+
creator.wallet -= session.bet; opponent.wallet += session.bet;
|
| 146 |
+
resultMsg += `🏆 Opponent wins +$${session.bet}`;
|
|
|
|
| 147 |
} else {
|
| 148 |
+
resultMsg += `🤝 Draw! No money lost.`;
|
| 149 |
}
|
| 150 |
+
await creator.save(); await opponent.save();
|
|
|
|
|
|
|
| 151 |
sessions.delete(gameId);
|
| 152 |
+
await editMsg(client, update.peer, update.msgId, resultMsg, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
};
|
| 154 |
|
| 155 |
module.exports = { startHack, startDice, handleCallback, handleDiceCallback };
|
handlers/inlineHandler.js
CHANGED
|
@@ -2,238 +2,145 @@ const { Api } = require("telegram");
|
|
| 2 |
const User = require('../models/User');
|
| 3 |
const sessions = require('../games/sessions');
|
| 4 |
const { buildMinesGrid } = require('./inlineGames');
|
|
|
|
|
|
|
| 5 |
|
| 6 |
const handleInlineCallback = async (client, update) => {
|
| 7 |
const data = update.data.toString();
|
| 8 |
const userId = update.userId.toString();
|
| 9 |
-
|
| 10 |
-
// All callback data uses "|" delimiter: type|gameId|value
|
| 11 |
const parts = data.split("|");
|
| 12 |
-
const gameType = parts[0];
|
| 13 |
const gameId = parts[1];
|
| 14 |
|
| 15 |
-
// --- MINES
|
| 16 |
-
// Data format: mines|gameId|row|col
|
| 17 |
if (gameType === 'mines') {
|
| 18 |
const session = sessions.get(gameId);
|
| 19 |
-
if (!session || session.type !== 'mines') return;
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
const r = parseInt(parts[2]);
|
| 23 |
-
const c = parseInt(parts[3]);
|
| 24 |
-
|
| 25 |
-
if (isNaN(r) || isNaN(c) || r < 0 || r > 2 || c < 0 || c > 2) return;
|
| 26 |
-
if (session.grid[r][c] !== '⬜') return;
|
| 27 |
|
| 28 |
const isMine = session.mines.some(m => m.r === r && m.c === c);
|
| 29 |
if (isMine) {
|
| 30 |
let user = await User.findOne({ userId });
|
| 31 |
if (!user) user = await User.create({ userId });
|
| 32 |
-
user.wallet -= session.bet;
|
| 33 |
-
await
|
| 34 |
-
await client.editMessage(update.peer, {
|
| 35 |
-
message: update.msgId,
|
| 36 |
-
text: `💥 **BOOM!** You hit a mine. Lost $${session.bet}.`,
|
| 37 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 38 |
-
});
|
| 39 |
sessions.delete(gameId);
|
| 40 |
} else {
|
| 41 |
session.grid[r][c] = '💎';
|
| 42 |
session.revealed++;
|
| 43 |
-
if (session.revealed === 7) {
|
| 44 |
let user = await User.findOne({ userId });
|
| 45 |
if (!user) user = await User.create({ userId });
|
| 46 |
-
|
| 47 |
-
await user.save();
|
| 48 |
-
await
|
| 49 |
-
message: update.msgId,
|
| 50 |
-
text: `🏆 **CLEAR!** You found all safe spots. Won $${session.bet * 5}!`,
|
| 51 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 52 |
-
});
|
| 53 |
sessions.delete(gameId);
|
| 54 |
} else {
|
| 55 |
-
await
|
| 56 |
-
message: update.msgId,
|
| 57 |
-
text: `💎 Safe! Revealed: ${session.revealed}/7`,
|
| 58 |
-
buttons: client.buildReplyMarkup(buildMinesGrid(gameId, session.grid))
|
| 59 |
-
});
|
| 60 |
}
|
| 61 |
}
|
| 62 |
return;
|
| 63 |
}
|
| 64 |
|
| 65 |
-
// --- ROULETTE
|
| 66 |
-
// Data format: roulette|gameId
|
| 67 |
if (gameType === 'roulette') {
|
| 68 |
let user = await User.findOne({ userId });
|
| 69 |
if (!user) user = await User.create({ userId });
|
| 70 |
const bet = 200;
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
user.isDead = true;
|
| 76 |
-
user.lastDeath = new Date();
|
| 77 |
-
user.health = 0;
|
| 78 |
-
await client.editMessage(update.peer, {
|
| 79 |
-
message: update.msgId,
|
| 80 |
-
text: `💥 **BANG!** You shot yourself. You are DEAD. Lost $${bet}.`,
|
| 81 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 82 |
-
});
|
| 83 |
} else {
|
| 84 |
-
user.wallet += bet * 2;
|
| 85 |
-
await
|
| 86 |
-
message: update.msgId,
|
| 87 |
-
text: `🚩 **CLICK.** The chamber was empty. You won $${bet * 2}!`,
|
| 88 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 89 |
-
});
|
| 90 |
}
|
| 91 |
-
await user.save();
|
| 92 |
return;
|
| 93 |
}
|
| 94 |
|
| 95 |
-
// --- HIGHER OR LOWER
|
| 96 |
-
// Data format: hl|gameId|higher or hl|gameId|lower
|
| 97 |
if (gameType === 'hl') {
|
| 98 |
const session = sessions.get(gameId);
|
| 99 |
-
if (!session || session.type !== 'hl') return;
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
const choice = parts[2]; // 'higher' or 'lower'
|
| 103 |
-
if (choice !== 'higher' && choice !== 'lower') return;
|
| 104 |
-
|
| 105 |
const nextNum = Math.floor(Math.random() * 13) + 1;
|
| 106 |
let user = await User.findOne({ userId });
|
| 107 |
if (!user) user = await User.create({ userId });
|
| 108 |
|
| 109 |
-
const isHigher = nextNum > session.lastNum;
|
| 110 |
-
const isWin = (choice === 'higher' && isHigher) || (choice === 'lower' && !isHigher && nextNum !== session.lastNum);
|
| 111 |
-
|
| 112 |
if (nextNum === session.lastNum) {
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
buttons: client.buildReplyMarkup([[
|
| 118 |
-
new Api.KeyboardButtonCallback({ text: "Higher ⬆️", data: Buffer.from(`hl|${gameId}|higher`) }),
|
| 119 |
-
new Api.KeyboardButtonCallback({ text: "Lower ⬇️", data: Buffer.from(`hl|${gameId}|lower`) })
|
| 120 |
-
]])
|
| 121 |
-
});
|
| 122 |
return;
|
| 123 |
}
|
| 124 |
-
|
| 125 |
if (isWin) {
|
| 126 |
-
session.streak++;
|
| 127 |
-
session.lastNum = nextNum;
|
| 128 |
if (session.streak >= 3) {
|
| 129 |
-
|
| 130 |
-
await user.save();
|
| 131 |
-
await
|
| 132 |
-
message: update.msgId,
|
| 133 |
-
text: `🏆 **STREAK!** The number was **${nextNum}**. You won $${session.bet * 3}!`,
|
| 134 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 135 |
-
});
|
| 136 |
sessions.delete(gameId);
|
| 137 |
} else {
|
| 138 |
-
await
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
new Api.KeyboardButtonCallback({ text: "Higher ⬆️", data: Buffer.from(`hl|${gameId}|higher`) }),
|
| 143 |
-
new Api.KeyboardButtonCallback({ text: "Lower ⬇️", data: Buffer.from(`hl|${gameId}|lower`) })
|
| 144 |
-
]])
|
| 145 |
-
});
|
| 146 |
}
|
| 147 |
} else {
|
| 148 |
-
user.wallet -= session.bet;
|
| 149 |
-
await
|
| 150 |
-
await client.editMessage(update.peer, {
|
| 151 |
-
message: update.msgId,
|
| 152 |
-
text: `❌ Wrong! The number was **${nextNum}**. You lost $${session.bet}.`,
|
| 153 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 154 |
-
});
|
| 155 |
sessions.delete(gameId);
|
| 156 |
}
|
| 157 |
return;
|
| 158 |
}
|
| 159 |
|
| 160 |
-
// --- FLIP
|
| 161 |
-
// Data format: flip|gameId|heads or flip|gameId|tails
|
| 162 |
if (gameType === 'flip') {
|
| 163 |
-
const side = parts[2];
|
| 164 |
-
if (side !== 'heads' && side !== 'tails') return;
|
| 165 |
-
|
| 166 |
const result = Math.random() > 0.5 ? 'heads' : 'tails';
|
| 167 |
let user = await User.findOne({ userId });
|
| 168 |
if (!user) user = await User.create({ userId });
|
| 169 |
const bet = 100;
|
| 170 |
-
|
| 171 |
if (side === result) {
|
| 172 |
-
user.wallet += bet;
|
| 173 |
-
await
|
| 174 |
-
message: update.msgId,
|
| 175 |
-
text: `🪙 Result: **${result.toUpperCase()}**\n✅ You won $${bet}!`,
|
| 176 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 177 |
-
});
|
| 178 |
} else {
|
| 179 |
-
user.wallet -= bet;
|
| 180 |
-
await
|
| 181 |
-
message: update.msgId,
|
| 182 |
-
text: `🪙 Result: **${result.toUpperCase()}**\n❌ You lost $${bet}!`,
|
| 183 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 184 |
-
});
|
| 185 |
}
|
| 186 |
-
await user.save();
|
| 187 |
return;
|
| 188 |
}
|
| 189 |
|
| 190 |
-
// --- RPS
|
| 191 |
-
// Data format: rps|gameId|rock/paper/scissors
|
| 192 |
if (gameType === 'rps') {
|
| 193 |
const session = sessions.get(gameId);
|
| 194 |
if (!session || session.type !== 'rps') return;
|
| 195 |
-
|
| 196 |
const move = parts[2];
|
| 197 |
-
if (
|
| 198 |
-
if (session.moves[userId]) return; // Already moved
|
| 199 |
-
|
| 200 |
session.moves[userId] = move;
|
| 201 |
const players = Object.keys(session.moves);
|
| 202 |
-
|
| 203 |
if (players.length === 2) {
|
| 204 |
-
const p1 = players
|
| 205 |
-
const
|
| 206 |
-
const m1 = session.moves[p1];
|
| 207 |
-
const m2 = session.moves[p2];
|
| 208 |
-
|
| 209 |
let winner = null;
|
| 210 |
if (m1 === m2) winner = 'draw';
|
| 211 |
-
else if ((m1
|
| 212 |
-
(m1 === 'paper' && m2 === 'rock') ||
|
| 213 |
-
(m1 === 'scissors' && m2 === 'paper')) winner = p1;
|
| 214 |
else winner = p2;
|
| 215 |
|
| 216 |
-
let msg = `✊✌️✋ **RPS RESULT**\n\
|
| 217 |
-
if (winner === 'draw') {
|
| 218 |
-
|
| 219 |
-
} else {
|
| 220 |
-
let w = await User.findOne({ userId: winner });
|
| 221 |
-
if (!w) w = await User.create({ userId: winner });
|
| 222 |
const loser = winner === p1 ? p2 : p1;
|
| 223 |
-
let
|
| 224 |
-
|
| 225 |
-
w.wallet += session.bet;
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
await l.save();
|
| 229 |
-
msg += `🏆 Winner: [${winner}](tg://user?id=${winner})! Won $${session.bet}`;
|
| 230 |
}
|
| 231 |
-
|
| 232 |
-
await client.editMessage(update.peer, {
|
| 233 |
-
message: update.msgId,
|
| 234 |
-
text: msg,
|
| 235 |
-
buttons: new Api.ReplyInlineMarkup({ rows: [] })
|
| 236 |
-
});
|
| 237 |
sessions.delete(gameId);
|
| 238 |
}
|
| 239 |
return;
|
|
|
|
| 2 |
const User = require('../models/User');
|
| 3 |
const sessions = require('../games/sessions');
|
| 4 |
const { buildMinesGrid } = require('./inlineGames');
|
| 5 |
+
const { editMsg } = require('../utils/editMsg');
|
| 6 |
+
const leveling = require('../utils/leveling');
|
| 7 |
|
| 8 |
const handleInlineCallback = async (client, update) => {
|
| 9 |
const data = update.data.toString();
|
| 10 |
const userId = update.userId.toString();
|
|
|
|
|
|
|
| 11 |
const parts = data.split("|");
|
| 12 |
+
const gameType = parts[0];
|
| 13 |
const gameId = parts[1];
|
| 14 |
|
| 15 |
+
// --- MINES ---
|
|
|
|
| 16 |
if (gameType === 'mines') {
|
| 17 |
const session = sessions.get(gameId);
|
| 18 |
+
if (!session || session.type !== 'mines' || session.userId !== userId) return;
|
| 19 |
+
const r = parseInt(parts[2]), c = parseInt(parts[3]);
|
| 20 |
+
if (isNaN(r) || isNaN(c) || session.grid[r][c] !== '⬜') return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
const isMine = session.mines.some(m => m.r === r && m.c === c);
|
| 23 |
if (isMine) {
|
| 24 |
let user = await User.findOne({ userId });
|
| 25 |
if (!user) user = await User.create({ userId });
|
| 26 |
+
user.wallet -= session.bet; await user.save();
|
| 27 |
+
await editMsg(client, update.peer, update.msgId, `💥 **BOOM!** You hit a mine. Lost $${session.bet}.`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
sessions.delete(gameId);
|
| 29 |
} else {
|
| 30 |
session.grid[r][c] = '💎';
|
| 31 |
session.revealed++;
|
| 32 |
+
if (session.revealed === 7) {
|
| 33 |
let user = await User.findOne({ userId });
|
| 34 |
if (!user) user = await User.create({ userId });
|
| 35 |
+
const prize = session.bet * 5;
|
| 36 |
+
user.wallet += prize; await user.save();
|
| 37 |
+
await editMsg(client, update.peer, update.msgId, `🏆 **CLEAR!** All safe spots found. Won $${prize}!`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
sessions.delete(gameId);
|
| 39 |
} else {
|
| 40 |
+
await editMsg(client, update.peer, update.msgId, `💎 Safe! Revealed: ${session.revealed}/7`, buildMinesGrid(gameId, session.grid));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
}
|
| 42 |
}
|
| 43 |
return;
|
| 44 |
}
|
| 45 |
|
| 46 |
+
// --- ROULETTE ---
|
|
|
|
| 47 |
if (gameType === 'roulette') {
|
| 48 |
let user = await User.findOne({ userId });
|
| 49 |
if (!user) user = await User.create({ userId });
|
| 50 |
const bet = 200;
|
| 51 |
+
if (Math.random() < 1/6) {
|
| 52 |
+
user.wallet -= bet; user.isDead = true; user.lastDeath = new Date(); user.health = 0;
|
| 53 |
+
await user.save();
|
| 54 |
+
await editMsg(client, update.peer, update.msgId, `💥 **BANG!** You are DEAD. Lost $${bet}.`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
} else {
|
| 56 |
+
user.wallet += bet * 2; await user.save();
|
| 57 |
+
await editMsg(client, update.peer, update.msgId, `🚩 **CLICK.** Empty chamber. Won $${bet * 2}!`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
}
|
|
|
|
| 59 |
return;
|
| 60 |
}
|
| 61 |
|
| 62 |
+
// --- HIGHER OR LOWER ---
|
|
|
|
| 63 |
if (gameType === 'hl') {
|
| 64 |
const session = sessions.get(gameId);
|
| 65 |
+
if (!session || session.type !== 'hl' || session.userId !== userId) return;
|
| 66 |
+
const choice = parts[2];
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
const nextNum = Math.floor(Math.random() * 13) + 1;
|
| 68 |
let user = await User.findOne({ userId });
|
| 69 |
if (!user) user = await User.create({ userId });
|
| 70 |
|
|
|
|
|
|
|
|
|
|
| 71 |
if (nextNum === session.lastNum) {
|
| 72 |
+
await editMsg(client, update.peer, update.msgId, `🔄 Same number (**${nextNum}**)! Try again.`, [
|
| 73 |
+
[new Api.KeyboardButtonCallback({ text: "Higher ⬆️", data: Buffer.from(`hl|${gameId}|higher`) }),
|
| 74 |
+
new Api.KeyboardButtonCallback({ text: "Lower ⬇️", data: Buffer.from(`hl|${gameId}|lower`) })]
|
| 75 |
+
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
return;
|
| 77 |
}
|
| 78 |
+
const isWin = (choice === 'higher' && nextNum > session.lastNum) || (choice === 'lower' && nextNum < session.lastNum);
|
| 79 |
if (isWin) {
|
| 80 |
+
session.streak++; session.lastNum = nextNum;
|
|
|
|
| 81 |
if (session.streak >= 3) {
|
| 82 |
+
const prize = session.bet * 3;
|
| 83 |
+
user.wallet += prize; await user.save();
|
| 84 |
+
await editMsg(client, update.peer, update.msgId, `🏆 **STREAK x3!** Number was **${nextNum}**. Won $${prize}!`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
sessions.delete(gameId);
|
| 86 |
} else {
|
| 87 |
+
await editMsg(client, update.peer, update.msgId, `✅ Correct! Was **${nextNum}**. Streak: ${session.streak}/3\nHigher or Lower than **${nextNum}**?`, [
|
| 88 |
+
[new Api.KeyboardButtonCallback({ text: "Higher ⬆️", data: Buffer.from(`hl|${gameId}|higher`) }),
|
| 89 |
+
new Api.KeyboardButtonCallback({ text: "Lower ⬇️", data: Buffer.from(`hl|${gameId}|lower`) })]
|
| 90 |
+
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
}
|
| 92 |
} else {
|
| 93 |
+
user.wallet -= session.bet; await user.save();
|
| 94 |
+
await editMsg(client, update.peer, update.msgId, `❌ Wrong! Was **${nextNum}**. Lost $${session.bet}.`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
sessions.delete(gameId);
|
| 96 |
}
|
| 97 |
return;
|
| 98 |
}
|
| 99 |
|
| 100 |
+
// --- FLIP ---
|
|
|
|
| 101 |
if (gameType === 'flip') {
|
| 102 |
+
const side = parts[2];
|
|
|
|
|
|
|
| 103 |
const result = Math.random() > 0.5 ? 'heads' : 'tails';
|
| 104 |
let user = await User.findOne({ userId });
|
| 105 |
if (!user) user = await User.create({ userId });
|
| 106 |
const bet = 100;
|
|
|
|
| 107 |
if (side === result) {
|
| 108 |
+
user.wallet += bet; await user.save();
|
| 109 |
+
await editMsg(client, update.peer, update.msgId, `🪙 **${result.toUpperCase()}** ✅ You won $${bet}!`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
} else {
|
| 111 |
+
user.wallet -= bet; await user.save();
|
| 112 |
+
await editMsg(client, update.peer, update.msgId, `🪙 **${result.toUpperCase()}** ❌ You lost $${bet}!`, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
}
|
|
|
|
| 114 |
return;
|
| 115 |
}
|
| 116 |
|
| 117 |
+
// --- RPS ---
|
|
|
|
| 118 |
if (gameType === 'rps') {
|
| 119 |
const session = sessions.get(gameId);
|
| 120 |
if (!session || session.type !== 'rps') return;
|
|
|
|
| 121 |
const move = parts[2];
|
| 122 |
+
if (session.moves[userId]) return;
|
|
|
|
|
|
|
| 123 |
session.moves[userId] = move;
|
| 124 |
const players = Object.keys(session.moves);
|
|
|
|
| 125 |
if (players.length === 2) {
|
| 126 |
+
const [p1, p2] = players;
|
| 127 |
+
const m1 = session.moves[p1], m2 = session.moves[p2];
|
|
|
|
|
|
|
|
|
|
| 128 |
let winner = null;
|
| 129 |
if (m1 === m2) winner = 'draw';
|
| 130 |
+
else if ((m1==='rock'&&m2==='scissors')||(m1==='paper'&&m2==='rock')||(m1==='scissors'&&m2==='paper')) winner = p1;
|
|
|
|
|
|
|
| 131 |
else winner = p2;
|
| 132 |
|
| 133 |
+
let msg = `✊✌️✋ **RPS RESULT**\n\nP1: ${m1} vs P2: ${m2}\n\n`;
|
| 134 |
+
if (winner === 'draw') { msg += "🤝 DRAW!"; }
|
| 135 |
+
else {
|
|
|
|
|
|
|
|
|
|
| 136 |
const loser = winner === p1 ? p2 : p1;
|
| 137 |
+
let w = await User.findOne({ userId: winner }) || await User.create({ userId: winner });
|
| 138 |
+
let l = await User.findOne({ userId: loser }) || await User.create({ userId: loser });
|
| 139 |
+
w.wallet += session.bet; l.wallet -= session.bet;
|
| 140 |
+
await w.save(); await l.save();
|
| 141 |
+
msg += `🏆 Winner! +$${session.bet}`;
|
|
|
|
|
|
|
| 142 |
}
|
| 143 |
+
await editMsg(client, update.peer, update.msgId, msg, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
sessions.delete(gameId);
|
| 145 |
}
|
| 146 |
return;
|
handlers/strategyGames.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const User = require('../models/User');
|
| 2 |
+
const Chess = require('../games/Chess');
|
| 3 |
+
const Checkers = require('../games/Checkers');
|
| 4 |
+
const sessions = require('../games/sessions');
|
| 5 |
+
const leveling = require('../utils/leveling');
|
| 6 |
+
|
| 7 |
+
// ── /chess <bet> ──
|
| 8 |
+
const startChess = async (client, event) => {
|
| 9 |
+
const bet = parseInt(event.message.message.split(" ")[1]) || 100;
|
| 10 |
+
const userId = event.message.senderId.toString();
|
| 11 |
+
let user = await User.findOne({ userId }) || await User.create({ userId });
|
| 12 |
+
if (user.wallet < bet) return event.message.respond({ message: "Insufficient funds!" });
|
| 13 |
+
|
| 14 |
+
const chatId = event.chatId.toString();
|
| 15 |
+
const key = `chess_${chatId}`;
|
| 16 |
+
if (sessions.get(key)) return event.message.respond({ message: "A chess game is already active here!" });
|
| 17 |
+
|
| 18 |
+
const game = new Chess(userId, bet);
|
| 19 |
+
sessions.set(key, game);
|
| 20 |
+
|
| 21 |
+
await event.message.respond({
|
| 22 |
+
message: `♔ **CHESS** ♚\nBet: $${bet}\nCreated by player. Use /join to play!\n\n♙ White: waiting...\n♟ Black: waiting...\n\nGame starts when opponent joins. Moves: \`/m e2 e4\``
|
| 23 |
+
});
|
| 24 |
+
};
|
| 25 |
+
|
| 26 |
+
// ── /checkers <bet> ──
|
| 27 |
+
const startCheckers = async (client, event) => {
|
| 28 |
+
const bet = parseInt(event.message.message.split(" ")[1]) || 100;
|
| 29 |
+
const userId = event.message.senderId.toString();
|
| 30 |
+
let user = await User.findOne({ userId }) || await User.create({ userId });
|
| 31 |
+
if (user.wallet < bet) return event.message.respond({ message: "Insufficient funds!" });
|
| 32 |
+
|
| 33 |
+
const chatId = event.chatId.toString();
|
| 34 |
+
const key = `checkers_${chatId}`;
|
| 35 |
+
if (sessions.get(key)) return event.message.respond({ message: "A checkers game is already active here!" });
|
| 36 |
+
|
| 37 |
+
const game = new Checkers(userId, bet);
|
| 38 |
+
sessions.set(key, game);
|
| 39 |
+
|
| 40 |
+
await event.message.respond({
|
| 41 |
+
message: `⛀ **CHECKERS (DRAUGHTS)** ⛂\nBet: $${bet}\nCreated by player. Use /join to play!\n\n⛀ White: waiting...\n⛂ Black: waiting...\n\nGame starts when opponent joins. Moves: \`/m a3 b4\`\nFlying Kings enabled! ⛁⛃`
|
| 42 |
+
});
|
| 43 |
+
};
|
| 44 |
+
|
| 45 |
+
// ── /join — also joins chess/checkers ──
|
| 46 |
+
const joinStrategy = async (client, event) => {
|
| 47 |
+
const chatId = event.chatId.toString();
|
| 48 |
+
const userId = event.message.senderId.toString();
|
| 49 |
+
|
| 50 |
+
for (const prefix of ['chess_', 'checkers_']) {
|
| 51 |
+
const key = prefix + chatId;
|
| 52 |
+
const game = sessions.get(key);
|
| 53 |
+
if (!game || game.status !== 'lobby') continue;
|
| 54 |
+
if (game.players.includes(userId)) continue;
|
| 55 |
+
|
| 56 |
+
let user = await User.findOne({ userId }) || await User.create({ userId });
|
| 57 |
+
if (user.wallet < game.bet) {
|
| 58 |
+
await event.message.respond({ message: `Not enough money! Need $${game.bet}` });
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
game.addPlayer(userId);
|
| 63 |
+
const type = prefix === 'chess_' ? 'Chess' : 'Checkers';
|
| 64 |
+
const icons = prefix === 'chess_' ? ['♙','♟'] : ['⛀','⛂'];
|
| 65 |
+
|
| 66 |
+
await client.sendMessage(chatId, {
|
| 67 |
+
message: `🚀 **${type.toUpperCase()} STARTED!**\nBet: $${game.bet}\n\n${icons[0]} White: Player 1\n${icons[1]} Black: Player 2\n\n${game.renderBoard()}\n\n${icons[0]} White's turn! Use \`/m <from> <to>\`\nExample: \`/m e2 e4\``
|
| 68 |
+
});
|
| 69 |
+
return;
|
| 70 |
+
}
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
// ── /m <from> <to> — make a move ──
|
| 74 |
+
const processMove = async (client, event) => {
|
| 75 |
+
const text = event.message.message;
|
| 76 |
+
if (!text.startsWith("/m ")) return;
|
| 77 |
+
|
| 78 |
+
const chatId = event.chatId.toString();
|
| 79 |
+
const userId = event.message.senderId.toString();
|
| 80 |
+
const parts = text.split(" ");
|
| 81 |
+
if (parts.length < 3) return event.message.respond({ message: "Usage: `/m e2 e4`" });
|
| 82 |
+
|
| 83 |
+
const fromStr = parts[1].toLowerCase();
|
| 84 |
+
const toStr = parts[2].toLowerCase();
|
| 85 |
+
|
| 86 |
+
// Try chess first, then checkers
|
| 87 |
+
for (const prefix of ['chess_', 'checkers_']) {
|
| 88 |
+
const key = prefix + chatId;
|
| 89 |
+
const game = sessions.get(key);
|
| 90 |
+
if (!game || game.status !== 'playing') continue;
|
| 91 |
+
|
| 92 |
+
const result = game.makeMove(userId, fromStr, toStr);
|
| 93 |
+
if (result.error) {
|
| 94 |
+
return event.message.respond({ message: `❌ ${result.error}` });
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
const type = prefix === 'chess_' ? 'Chess' : 'Checkers';
|
| 98 |
+
const icons = prefix === 'chess_' ? ['♙','♟'] : ['⛀','⛂'];
|
| 99 |
+
|
| 100 |
+
// Check winner
|
| 101 |
+
const winner = game.checkWinner();
|
| 102 |
+
if (winner) {
|
| 103 |
+
let msg = "";
|
| 104 |
+
if (winner === 'draw') {
|
| 105 |
+
msg = `🤝 **${type.toUpperCase()} — DRAW!**\n\n${game.renderBoard()}\n\nMoney returned.`;
|
| 106 |
+
} else {
|
| 107 |
+
const loserId = game.players.find(id => id !== winner);
|
| 108 |
+
let w = await User.findOne({ userId: winner }) || await User.create({ userId: winner });
|
| 109 |
+
let l = await User.findOne({ userId: loserId }) || await User.create({ userId: loserId });
|
| 110 |
+
w.wallet += game.bet; l.wallet -= game.bet;
|
| 111 |
+
const xp = await leveling.addXP(winner, 200);
|
| 112 |
+
await w.save(); await l.save();
|
| 113 |
+
|
| 114 |
+
msg = `🏆 **${type.toUpperCase()} — GAME OVER!**\n\n${game.renderBoard()}\n\n🎉 Winner gets $${game.bet} + 200 XP!`;
|
| 115 |
+
if (xp.leveledUp) msg += `\n🆙 Level ${xp.level}!`;
|
| 116 |
+
}
|
| 117 |
+
await client.sendMessage(chatId, { message: msg });
|
| 118 |
+
sessions.delete(key);
|
| 119 |
+
return;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
// Show board + next turn
|
| 123 |
+
const turnIdx = game.turn;
|
| 124 |
+
const captured = result.captured ? ` (captured ${result.captured})` : (result.capture ? " 💥 Capture!" : "");
|
| 125 |
+
await client.sendMessage(chatId, {
|
| 126 |
+
message: `${icons[game.turn === 0 ? 0 : 1]} **${type}** — Move: ${fromStr}→${toStr}${captured}\n\n${game.renderBoard()}\n\n${icons[turnIdx]} Player ${turnIdx + 1}'s turn! Use \`/m <from> <to>\``
|
| 127 |
+
});
|
| 128 |
+
return;
|
| 129 |
+
}
|
| 130 |
+
};
|
| 131 |
+
|
| 132 |
+
// ── /surrender — forfeit current game ──
|
| 133 |
+
const surrender = async (client, event) => {
|
| 134 |
+
const chatId = event.chatId.toString();
|
| 135 |
+
const userId = event.message.senderId.toString();
|
| 136 |
+
|
| 137 |
+
for (const prefix of ['chess_', 'checkers_']) {
|
| 138 |
+
const key = prefix + chatId;
|
| 139 |
+
const game = sessions.get(key);
|
| 140 |
+
if (!game || game.status !== 'playing') continue;
|
| 141 |
+
if (!game.players.includes(userId)) continue;
|
| 142 |
+
|
| 143 |
+
const winner = game.players.find(id => id !== userId);
|
| 144 |
+
let w = await User.findOne({ userId: winner }) || await User.create({ userId: winner });
|
| 145 |
+
let l = await User.findOne({ userId: userId }) || await User.create({ userId: userId });
|
| 146 |
+
w.wallet += game.bet; l.wallet -= game.bet;
|
| 147 |
+
await leveling.addXP(winner, 100);
|
| 148 |
+
await w.save(); await l.save();
|
| 149 |
+
|
| 150 |
+
const type = prefix === 'chess_' ? 'Chess' : 'Checkers';
|
| 151 |
+
await client.sendMessage(chatId, {
|
| 152 |
+
message: `🏳️ **${type.toUpperCase()} — SURRENDER!**\n\nA player surrendered. Winner gets $${game.bet}!`
|
| 153 |
+
});
|
| 154 |
+
sessions.delete(key);
|
| 155 |
+
return;
|
| 156 |
+
}
|
| 157 |
+
};
|
| 158 |
+
|
| 159 |
+
module.exports = { startChess, startCheckers, joinStrategy, processMove, surrender };
|
models/User.js
CHANGED
|
@@ -17,6 +17,10 @@ const UserSchema = new mongoose.Schema({
|
|
| 17 |
lastKill: { type: Date, default: null },
|
| 18 |
xp: { type: Number, default: 0 },
|
| 19 |
level: { type: Number, default: 1 },
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
});
|
| 21 |
|
| 22 |
module.exports = mongoose.model('User', UserSchema);
|
|
|
|
| 17 |
lastKill: { type: Date, default: null },
|
| 18 |
xp: { type: Number, default: 0 },
|
| 19 |
level: { type: Number, default: 1 },
|
| 20 |
+
// Leaderboard stats
|
| 21 |
+
kills: { type: Number, default: 0 },
|
| 22 |
+
robs: { type: Number, default: 0 },
|
| 23 |
+
totalEarned: { type: Number, default: 0 },
|
| 24 |
});
|
| 25 |
|
| 26 |
module.exports = mongoose.model('User', UserSchema);
|
utils/editMsg.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Helper to edit a message and optionally clear inline buttons.
|
| 3 |
+
* Uses raw client.invoke to avoid GramJS wrapper issues with empty ReplyInlineMarkup.
|
| 4 |
+
*/
|
| 5 |
+
const { Api } = require("telegram");
|
| 6 |
+
|
| 7 |
+
/**
|
| 8 |
+
* Edit message text and optionally set/clear buttons.
|
| 9 |
+
* @param {TelegramClient} client
|
| 10 |
+
* @param {*} peer - The peer (from update.peer or chatId)
|
| 11 |
+
* @param {number} msgId - Message ID to edit
|
| 12 |
+
* @param {string} text - New message text
|
| 13 |
+
* @param {Array|null} buttons - 2D array of buttons, or null to remove all buttons
|
| 14 |
+
*/
|
| 15 |
+
async function editMsg(client, peer, msgId, text, buttons) {
|
| 16 |
+
try {
|
| 17 |
+
let replyMarkup;
|
| 18 |
+
if (buttons === null || buttons === undefined) {
|
| 19 |
+
// Remove all inline buttons
|
| 20 |
+
replyMarkup = new Api.ReplyInlineMarkup({ rows: [] });
|
| 21 |
+
} else if (Array.isArray(buttons) && buttons.length > 0) {
|
| 22 |
+
replyMarkup = client.buildReplyMarkup(buttons);
|
| 23 |
+
} else {
|
| 24 |
+
replyMarkup = new Api.ReplyInlineMarkup({ rows: [] });
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
const inputPeer = await client.getInputEntity(peer);
|
| 28 |
+
await client.invoke(new Api.messages.EditMessage({
|
| 29 |
+
peer: inputPeer,
|
| 30 |
+
id: msgId,
|
| 31 |
+
message: text,
|
| 32 |
+
replyMarkup: replyMarkup,
|
| 33 |
+
}));
|
| 34 |
+
} catch (e) {
|
| 35 |
+
// If REPLY_MARKUP_INVALID, retry without markup (just edit text, buttons stay)
|
| 36 |
+
if (e.message && e.message.includes('REPLY_MARKUP_INVALID')) {
|
| 37 |
+
try {
|
| 38 |
+
const inputPeer = await client.getInputEntity(peer);
|
| 39 |
+
await client.invoke(new Api.messages.EditMessage({
|
| 40 |
+
peer: inputPeer,
|
| 41 |
+
id: msgId,
|
| 42 |
+
message: text,
|
| 43 |
+
}));
|
| 44 |
+
} catch (e2) {
|
| 45 |
+
console.error('[editMsg] fallback failed:', e2.message);
|
| 46 |
+
}
|
| 47 |
+
} else {
|
| 48 |
+
console.error('[editMsg] error:', e.message);
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
module.exports = { editMsg };
|
webapp/index.html
CHANGED
|
@@ -3,596 +3,618 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 6 |
-
<title>
|
| 7 |
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
| 8 |
-
|
| 9 |
-
<!-- Unified OnClickA Loader -->
|
| 10 |
<script async src="https://js.onclckmn.com/static/onclicka.js" data-admpid="443657"></script>
|
| 11 |
-
<!-- OnClickA TMA SDK -->
|
| 12 |
<script src="https://js.onclckvd.com/in-stream-ad-admanager/tma.js"></script>
|
| 13 |
-
|
| 14 |
-
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;700&display=swap" rel="stylesheet">
|
| 15 |
<style>
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
--card-
|
| 19 |
-
--accent-
|
| 20 |
-
--
|
| 21 |
-
--
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
--
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
display:
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
-
|
| 48 |
-
|
| 49 |
-
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
.
|
| 85 |
-
font-size:
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
.
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
background:
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
text-align:
|
| 136 |
-
|
| 137 |
-
}
|
| 138 |
-
.
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
.
|
| 163 |
-
|
| 164 |
-
background:
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
}
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
</style>
|
| 179 |
</head>
|
| 180 |
<body>
|
| 181 |
-
<div class="header">
|
| 182 |
-
<h1>🎁 Reward Center</h1>
|
| 183 |
-
<p>Watch ads • Earn coins • Support the game</p>
|
| 184 |
-
</div>
|
| 185 |
-
|
| 186 |
-
<!-- Hub -->
|
| 187 |
-
<div id="hub-view" class="hub">
|
| 188 |
-
<div class="reward-card">
|
| 189 |
-
<h2>💎 Daily Reward</h2>
|
| 190 |
-
<div class="amount">$3,000</div>
|
| 191 |
-
<p>Claim your daily bonus once every 24 hours.<br>Requires watching a short rewarded ad.</p>
|
| 192 |
-
<button id="daily-btn" class="btn" onclick="selectDaily()">🚀 Claim Daily</button>
|
| 193 |
-
<div id="daily-status" class="status-text" style="margin-top:8px; font-size:12px;"></div>
|
| 194 |
-
</div>
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
</div>
|
| 203 |
</div>
|
| 204 |
|
| 205 |
-
<!--
|
| 206 |
-
<div
|
| 207 |
-
<div class="
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
</div>
|
| 213 |
-
<div id="daily-timer-ui" style="display:none;">
|
| 214 |
-
<div class="timer-val" id="daily-sec">30</div>
|
| 215 |
-
<div class="progress-box">
|
| 216 |
-
<div id="daily-progress" class="progress-bar"></div>
|
| 217 |
-
</div>
|
| 218 |
-
<p>Verification in progress...</p>
|
| 219 |
-
</div>
|
| 220 |
-
<button id="daily-claim-btn" class="btn success" style="display:none; margin-top: 10px;" onclick="handleDailyClaim()">🎁 Claim Your Reward $3,000</button>
|
| 221 |
-
|
| 222 |
-
<div id="daily-result-view" style="display:none;">
|
| 223 |
-
<span class="icon-badge">✅</span>
|
| 224 |
-
<h2 style="margin:8px 0;">Success!</h2>
|
| 225 |
-
<p>Your <span style="color:var(--accent-orange);font-weight:800;">$3000</span> reward added to wallet.</p>
|
| 226 |
-
<p style="font-size:12px;">Closing in 3s...</p>
|
| 227 |
-
</div>
|
| 228 |
-
<div id="daily-error-view" style="display:none;">
|
| 229 |
-
<span class="icon-badge">❌</span>
|
| 230 |
-
<h2 style="margin:8px 0;">Error</h2>
|
| 231 |
-
<p id="daily-error-msg" style="color:#ff6b6b;"></p>
|
| 232 |
-
</div>
|
| 233 |
-
<button class="btn back-btn" onclick="backToHub()">← Back to Rewards</button>
|
| 234 |
</div>
|
| 235 |
</div>
|
| 236 |
|
| 237 |
-
<!--
|
| 238 |
-
<div
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
</div>
|
| 246 |
-
<
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
<p>Verification in progress...</p>
|
| 252 |
-
</div>
|
| 253 |
-
<button id="mission-claim-btn" class="btn mission" style="display:none; margin-top: 10px;" onclick="handleMissionClaim()">🎁 Claim $200 Mission Reward</button>
|
| 254 |
-
<div id="mission-success-view" style="display:none; text-align:center;">
|
| 255 |
-
<span class="icon-badge">🏆</span>
|
| 256 |
-
<h2 style="margin-bottom:8px;">Mission Complete!</h2>
|
| 257 |
-
<p>$200 added to your wallet.</p>
|
| 258 |
-
<button class="btn" style="margin-top:12px;" onclick="resetMission()">Watch Another</button>
|
| 259 |
-
<button class="btn back-btn" style="margin-top:8px;" onclick="backToHub()">Back to Hub</button>
|
| 260 |
-
</div>
|
| 261 |
-
<div id="mission-error-view" style="display:none;">
|
| 262 |
-
<span class="icon-badge">❌</span>
|
| 263 |
-
<p id="mission-error-msg" style="color:#ff6b6b;"></p>
|
| 264 |
-
</div>
|
| 265 |
-
<button class="btn back-btn" onclick="backToHub()">← Back to Rewards</button>
|
| 266 |
</div>
|
| 267 |
</div>
|
|
|
|
| 268 |
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
// KeyboardButtonWebView (inline button) provides full initData with user id.
|
| 276 |
-
// Direct browser access won't have it.
|
| 277 |
-
if (!webapp || !webapp.initData || !webapp.initDataUnsafe || !webapp.initDataUnsafe.user || !webapp.initDataUnsafe.user.id) {
|
| 278 |
-
document.body.innerHTML = `
|
| 279 |
-
<div style="padding:40px 20px; text-align:center; font-family: 'Outfit', sans-serif; background:#0c0f1d; color:#fff; min-height:100vh;">
|
| 280 |
-
<h1 style="font-size:28px; margin-bottom:10px;">🔒 Access Blocked</h1>
|
| 281 |
-
<p style="font-size:16px; max-width:320px; margin:0 auto 20px;">This reward mini-app page <strong>requires</strong> being opened from inside Telegram as a Mini App.</p>
|
| 282 |
-
<p style="margin-bottom:20px;">Please go to <a href="https://t.me/Alexagamebot" style="color:#2481cc; text-decoration:underline;">@Alexagamebot</a> and send <strong>/daily</strong> or <strong>/missions</strong> to open it correctly.</p>
|
| 283 |
-
<p style="font-size:12px; color:#94a3b8; border-top:1px solid rgba(255,255,255,0.1); padding-top:15px; margin-top:30px;">
|
| 284 |
-
No Telegram user ID / initData detected.<br>
|
| 285 |
-
Direct links or browser access are intentionally blocked for security and to enforce daily limits.
|
| 286 |
-
</p>
|
| 287 |
-
</div>
|
| 288 |
-
`;
|
| 289 |
-
throw new Error("Blocked: no TG initData");
|
| 290 |
-
}
|
| 291 |
-
|
| 292 |
-
const API_BASE = window.location.origin;
|
| 293 |
-
const REWARD_SPOT = "6120581";
|
| 294 |
-
const INPAGE_SPOT = "6120578";
|
| 295 |
-
const MISSION_SPOT = "6120581";
|
| 296 |
-
|
| 297 |
-
let triggerRewardAd = null;
|
| 298 |
-
let triggerInPageAd = null;
|
| 299 |
-
let triggerMissionAd = null;
|
| 300 |
-
let dailyTimeLeft = 30;
|
| 301 |
-
let missionAdsLeft = 50;
|
| 302 |
-
|
| 303 |
-
// ── Helper: call backend API with initData auth ──
|
| 304 |
-
async function apiCall(method, path, body) {
|
| 305 |
-
const opts = {
|
| 306 |
-
method,
|
| 307 |
-
headers: {
|
| 308 |
-
'X-Init-Data': webapp.initData,
|
| 309 |
-
'Content-Type': 'application/json'
|
| 310 |
-
}
|
| 311 |
-
};
|
| 312 |
-
if (body) opts.body = JSON.stringify(body);
|
| 313 |
-
const res = await fetch(`${API_BASE}${path}`, opts);
|
| 314 |
-
return res.json();
|
| 315 |
-
}
|
| 316 |
-
|
| 317 |
-
async function updateHubStatus() {
|
| 318 |
-
if (!webapp.initData) return;
|
| 319 |
-
|
| 320 |
-
try {
|
| 321 |
-
const d = await apiCall('GET', '/status?type=daily');
|
| 322 |
-
const dBtn = document.getElementById('daily-btn');
|
| 323 |
-
const dStatus = document.getElementById('daily-status');
|
| 324 |
-
if (!d.canClaim) {
|
| 325 |
-
dBtn.disabled = true;
|
| 326 |
-
dBtn.textContent = '⏳ Claimed';
|
| 327 |
-
dStatus.innerHTML = 'Come back tomorrow after 00:00';
|
| 328 |
-
} else {
|
| 329 |
-
dBtn.disabled = false;
|
| 330 |
-
dBtn.textContent = '🚀 Claim Daily';
|
| 331 |
-
dStatus.innerHTML = 'Available now!';
|
| 332 |
-
}
|
| 333 |
-
} catch(e){}
|
| 334 |
-
|
| 335 |
-
try {
|
| 336 |
-
const m = await apiCall('GET', '/status?type=mission_inpage');
|
| 337 |
-
const mBtn = document.getElementById('mission-btn');
|
| 338 |
-
const mStatus = document.getElementById('mission-status');
|
| 339 |
-
missionAdsLeft = m.adsLeft ?? 50;
|
| 340 |
-
if (missionAdsLeft <= 0) {
|
| 341 |
-
mBtn.disabled = true;
|
| 342 |
-
mBtn.textContent = '✅ Limit Reached';
|
| 343 |
-
mStatus.innerHTML = '50/50 today. Reset tomorrow.';
|
| 344 |
-
} else {
|
| 345 |
-
mBtn.disabled = false;
|
| 346 |
-
mBtn.textContent = '🎯 Start Missions';
|
| 347 |
-
mStatus.innerHTML = `${missionAdsLeft} ads left today`;
|
| 348 |
-
}
|
| 349 |
-
} catch(e){}
|
| 350 |
-
}
|
| 351 |
-
|
| 352 |
-
// Init ads
|
| 353 |
-
window.initCdTma?.({ id: REWARD_SPOT })
|
| 354 |
-
.then(show => { triggerRewardAd = show; console.log('Rewarded ready'); })
|
| 355 |
-
.catch(e => console.error(e));
|
| 356 |
-
|
| 357 |
-
window.initCdTma?.({ id: INPAGE_SPOT })
|
| 358 |
-
.then(show => { triggerInPageAd = show; console.log('Inpage ready'); })
|
| 359 |
-
.catch(e => console.error(e));
|
| 360 |
-
|
| 361 |
-
window.initCdTma?.({ id: MISSION_SPOT })
|
| 362 |
-
.then(show => { triggerMissionAd = show; console.log('Mission ad ready'); })
|
| 363 |
-
.catch(e => console.error(e));
|
| 364 |
-
|
| 365 |
-
// ── Navigation ──
|
| 366 |
-
function selectDaily() {
|
| 367 |
-
document.getElementById('hub-view').style.display = 'none';
|
| 368 |
-
document.getElementById('daily-flow').style.display = 'block';
|
| 369 |
-
document.getElementById('mission-flow').style.display = 'none';
|
| 370 |
-
document.getElementById('daily-setup').style.display = 'block';
|
| 371 |
-
document.getElementById('daily-timer-ui').style.display = 'none';
|
| 372 |
-
document.getElementById('daily-claim-btn').style.display = 'none';
|
| 373 |
-
document.getElementById('daily-result-view').style.display = 'none';
|
| 374 |
-
document.getElementById('daily-error-view').style.display = 'none';
|
| 375 |
-
document.getElementById('daily-start-btn').disabled = false;
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
function selectMission() {
|
| 379 |
-
document.getElementById('hub-view').style.display = 'none';
|
| 380 |
-
document.getElementById('daily-flow').style.display = 'none';
|
| 381 |
-
document.getElementById('mission-flow').style.display = 'block';
|
| 382 |
-
document.getElementById('mission-setup').style.display = 'block';
|
| 383 |
-
document.getElementById('mission-timer-ui').style.display = 'none';
|
| 384 |
-
document.getElementById('mission-claim-btn').style.display = 'none';
|
| 385 |
-
document.getElementById('mission-success-view').style.display = 'none';
|
| 386 |
-
document.getElementById('mission-error-view').style.display = 'none';
|
| 387 |
-
updateMissionUI();
|
| 388 |
-
}
|
| 389 |
-
|
| 390 |
-
function backToHub() {
|
| 391 |
-
document.getElementById('hub-view').style.display = 'flex';
|
| 392 |
-
document.getElementById('daily-flow').style.display = 'none';
|
| 393 |
-
document.getElementById('mission-flow').style.display = 'none';
|
| 394 |
-
updateHubStatus();
|
| 395 |
-
}
|
| 396 |
-
|
| 397 |
-
// ── Daily status check ──
|
| 398 |
-
async function checkDailyStatus() {
|
| 399 |
-
try {
|
| 400 |
-
const data = await apiCall('GET', '/status?type=daily');
|
| 401 |
-
if (!data.canClaim) {
|
| 402 |
-
document.getElementById('daily-status-text').innerHTML = `Already claimed today!<br><span style="font-size:12px">Come back tomorrow after 00:00!</span>`;
|
| 403 |
-
document.getElementById('daily-start-btn').style.display = 'none';
|
| 404 |
-
document.getElementById('daily-claim-btn').style.display = 'none';
|
| 405 |
-
return false;
|
| 406 |
-
}
|
| 407 |
-
return true;
|
| 408 |
-
} catch (e) {
|
| 409 |
-
console.error('checkDailyStatus error', e);
|
| 410 |
-
document.getElementById('daily-status-text').innerHTML = '⏳ Verification failed. Please reopen from /daily in bot.';
|
| 411 |
-
document.getElementById('daily-start-btn').style.display = 'none';
|
| 412 |
-
return false;
|
| 413 |
-
}
|
| 414 |
-
}
|
| 415 |
-
|
| 416 |
-
function updateMissionUI() {
|
| 417 |
-
const info = document.getElementById('mission-progress-info');
|
| 418 |
-
info.textContent = `${50 - missionAdsLeft} / 50 ads watched today`;
|
| 419 |
-
const btn = document.getElementById('mission-start-btn');
|
| 420 |
-
if (missionAdsLeft <= 0) {
|
| 421 |
-
btn.disabled = true;
|
| 422 |
-
btn.textContent = '✅ Daily Limit Reached';
|
| 423 |
-
} else {
|
| 424 |
-
btn.disabled = !triggerMissionAd;
|
| 425 |
-
btn.textContent = triggerMissionAd ? '🎯 Watch Ad & Earn $200' : '⌛ Loading...';
|
| 426 |
-
}
|
| 427 |
-
}
|
| 428 |
-
|
| 429 |
-
// ── Daily flow ──
|
| 430 |
-
async function startDailyFlow() {
|
| 431 |
-
if (!triggerRewardAd) {
|
| 432 |
-
alert("Ad loading, please try again.");
|
| 433 |
-
return;
|
| 434 |
-
}
|
| 435 |
-
const can = await checkDailyStatus();
|
| 436 |
-
if (!can) return;
|
| 437 |
-
|
| 438 |
-
document.getElementById('daily-start-btn').disabled = true;
|
| 439 |
-
|
| 440 |
-
triggerRewardAd()
|
| 441 |
-
.then(() => {
|
| 442 |
-
document.getElementById('daily-setup').style.display = 'none';
|
| 443 |
-
document.getElementById('daily-timer-ui').style.display = 'block';
|
| 444 |
-
dailyTimeLeft = 30;
|
| 445 |
-
document.getElementById('daily-sec').innerText = dailyTimeLeft;
|
| 446 |
-
document.getElementById('daily-progress').style.width = '0%';
|
| 447 |
-
|
| 448 |
-
if (triggerInPageAd) {
|
| 449 |
-
triggerInPageAd().catch(e => console.error("Inpage failed", e));
|
| 450 |
-
}
|
| 451 |
-
|
| 452 |
-
const countdown = setInterval(() => {
|
| 453 |
-
dailyTimeLeft--;
|
| 454 |
-
document.getElementById('daily-sec').innerText = dailyTimeLeft;
|
| 455 |
-
document.getElementById('daily-progress').style.width = ((30 - dailyTimeLeft) / 30 * 100) + '%';
|
| 456 |
-
|
| 457 |
-
if (dailyTimeLeft <= 0) {
|
| 458 |
-
clearInterval(countdown);
|
| 459 |
-
document.getElementById('daily-timer-ui').style.display = 'none';
|
| 460 |
-
document.getElementById('daily-claim-btn').style.display = 'block';
|
| 461 |
-
}
|
| 462 |
-
}, 1000);
|
| 463 |
-
})
|
| 464 |
-
.catch(() => {
|
| 465 |
-
document.getElementById('daily-start-btn').disabled = false;
|
| 466 |
-
});
|
| 467 |
-
}
|
| 468 |
-
|
| 469 |
-
// ── Claim daily reward via HTTP POST /claim (not sendData!) ──
|
| 470 |
-
async function handleDailyClaim() {
|
| 471 |
-
document.getElementById('daily-claim-btn').disabled = true;
|
| 472 |
-
document.getElementById('daily-claim-btn').textContent = '⏳ Claiming...';
|
| 473 |
-
|
| 474 |
-
try {
|
| 475 |
-
const result = await apiCall('POST', '/claim', { type: 'daily' });
|
| 476 |
-
if (result.ok) {
|
| 477 |
-
document.getElementById('daily-claim-btn').style.display = 'none';
|
| 478 |
-
document.getElementById('daily-result-view').style.display = 'block';
|
| 479 |
-
if (webapp.HapticFeedback) webapp.HapticFeedback.notificationOccurred('success');
|
| 480 |
-
setTimeout(() => { webapp.close(); }, 3000);
|
| 481 |
-
} else {
|
| 482 |
-
document.getElementById('daily-claim-btn').style.display = 'none';
|
| 483 |
-
document.getElementById('daily-error-view').style.display = 'block';
|
| 484 |
-
document.getElementById('daily-error-msg').textContent = result.error || 'Claim failed';
|
| 485 |
-
}
|
| 486 |
-
} catch (e) {
|
| 487 |
-
document.getElementById('daily-claim-btn').style.display = 'none';
|
| 488 |
-
document.getElementById('daily-error-view').style.display = 'block';
|
| 489 |
-
document.getElementById('daily-error-msg').textContent = 'Network error. Try again.';
|
| 490 |
-
}
|
| 491 |
-
}
|
| 492 |
-
|
| 493 |
-
// ── Claim mission reward via HTTP POST /claim ──
|
| 494 |
-
async function handleMissionClaim() {
|
| 495 |
-
document.getElementById('mission-claim-btn').disabled = true;
|
| 496 |
-
document.getElementById('mission-claim-btn').textContent = '⏳ Claiming...';
|
| 497 |
-
|
| 498 |
-
try {
|
| 499 |
-
const result = await apiCall('POST', '/claim', { type: 'mission' });
|
| 500 |
-
if (result.ok) {
|
| 501 |
-
document.getElementById('mission-timer-ui').style.display = 'none';
|
| 502 |
-
document.getElementById('mission-claim-btn').style.display = 'none';
|
| 503 |
-
document.getElementById('mission-success-view').style.display = 'block';
|
| 504 |
-
if (webapp.HapticFeedback) webapp.HapticFeedback.notificationOccurred('success');
|
| 505 |
-
missionAdsLeft = result.adsLeft ?? Math.max(0, missionAdsLeft - 1);
|
| 506 |
-
setTimeout(() => { webapp.close(); }, 3000);
|
| 507 |
-
} else {
|
| 508 |
-
document.getElementById('mission-claim-btn').style.display = 'none';
|
| 509 |
-
document.getElementById('mission-error-view').style.display = 'block';
|
| 510 |
-
document.getElementById('mission-error-msg').textContent = result.error || 'Claim failed';
|
| 511 |
-
}
|
| 512 |
-
} catch (e) {
|
| 513 |
-
document.getElementById('mission-claim-btn').style.display = 'none';
|
| 514 |
-
document.getElementById('mission-error-view').style.display = 'block';
|
| 515 |
-
document.getElementById('mission-error-msg').textContent = 'Network error. Try again.';
|
| 516 |
-
}
|
| 517 |
-
}
|
| 518 |
-
|
| 519 |
-
// ── Mission flow ──
|
| 520 |
-
async function startMissionFlow() {
|
| 521 |
-
if (!triggerMissionAd) {
|
| 522 |
-
alert("Ad loading, try again.");
|
| 523 |
-
return;
|
| 524 |
-
}
|
| 525 |
-
try {
|
| 526 |
-
const m = await apiCall('GET', '/status?type=mission_inpage');
|
| 527 |
-
if (!m.canClaim || (m.adsLeft ?? 0) <= 0) {
|
| 528 |
-
alert("⏳ You've reached the limit of 50 ads for today. Come back tomorrow after 00:00!");
|
| 529 |
-
backToHub();
|
| 530 |
-
return;
|
| 531 |
-
}
|
| 532 |
-
missionAdsLeft = m.adsLeft ?? 50;
|
| 533 |
-
} catch (e) {
|
| 534 |
-
console.error(e);
|
| 535 |
-
alert("Status check failed. Try again.");
|
| 536 |
-
return;
|
| 537 |
-
}
|
| 538 |
-
|
| 539 |
-
const btn = document.getElementById('mission-start-btn');
|
| 540 |
-
btn.disabled = true;
|
| 541 |
-
btn.textContent = "⌛ Watching...";
|
| 542 |
-
|
| 543 |
-
triggerMissionAd()
|
| 544 |
-
.then(() => {
|
| 545 |
-
document.getElementById('mission-setup').style.display = 'none';
|
| 546 |
-
document.getElementById('mission-timer-ui').style.display = 'block';
|
| 547 |
-
let mTimeLeft = 30;
|
| 548 |
-
document.getElementById('mission-sec').innerText = mTimeLeft;
|
| 549 |
-
document.getElementById('mission-progress').style.width = '0%';
|
| 550 |
-
|
| 551 |
-
const mCountdown = setInterval(() => {
|
| 552 |
-
mTimeLeft--;
|
| 553 |
-
document.getElementById('mission-sec').innerText = mTimeLeft;
|
| 554 |
-
document.getElementById('mission-progress').style.width = ((30 - mTimeLeft) / 30 * 100) + '%';
|
| 555 |
-
|
| 556 |
-
if (mTimeLeft <= 0) {
|
| 557 |
-
clearInterval(mCountdown);
|
| 558 |
-
document.getElementById('mission-timer-ui').style.display = 'none';
|
| 559 |
-
document.getElementById('mission-claim-btn').style.display = 'block';
|
| 560 |
-
document.getElementById('mission-claim-btn').disabled = false;
|
| 561 |
-
document.getElementById('mission-claim-btn').textContent = '🎁 Claim $200 Mission Reward';
|
| 562 |
-
}
|
| 563 |
-
}, 1000);
|
| 564 |
-
})
|
| 565 |
-
.catch(() => {
|
| 566 |
-
btn.disabled = false;
|
| 567 |
-
btn.textContent = "🎯 Watch Ad & Earn $200";
|
| 568 |
-
});
|
| 569 |
-
}
|
| 570 |
-
|
| 571 |
-
function resetMission() {
|
| 572 |
-
document.getElementById('mission-success-view').style.display = 'none';
|
| 573 |
-
document.getElementById('mission-timer-ui').style.display = 'none';
|
| 574 |
-
document.getElementById('mission-claim-btn').style.display = 'none';
|
| 575 |
-
document.getElementById('mission-error-view').style.display = 'none';
|
| 576 |
-
document.getElementById('mission-setup').style.display = 'block';
|
| 577 |
-
updateMissionUI();
|
| 578 |
-
}
|
| 579 |
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
|
|
|
| 591 |
|
| 592 |
-
|
| 593 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
|
| 595 |
-
|
| 596 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
</body>
|
| 598 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 6 |
+
<title>Alexa Game</title>
|
| 7 |
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
|
|
|
|
|
|
| 8 |
<script async src="https://js.onclckmn.com/static/onclicka.js" data-admpid="443657"></script>
|
|
|
|
| 9 |
<script src="https://js.onclckvd.com/in-stream-ad-admanager/tma.js"></script>
|
|
|
|
|
|
|
| 10 |
<style>
|
| 11 |
+
*{margin:0;padding:0;box-sizing:border-box;}
|
| 12 |
+
:root{
|
| 13 |
+
--bg:#0a0e1a;--card:#141929;--card2:#1a2035;
|
| 14 |
+
--accent:#3b82f6;--green:#22c55e;--orange:#f59e0b;--red:#ef4444;
|
| 15 |
+
--text:#f1f5f9;--dim:#64748b;--border:rgba(255,255,255,.08);
|
| 16 |
+
--radius:16px;
|
| 17 |
+
}
|
| 18 |
+
body{
|
| 19 |
+
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
|
| 20 |
+
background:var(--bg);color:var(--text);
|
| 21 |
+
min-height:100vh;display:flex;flex-direction:column;
|
| 22 |
+
-webkit-user-select:none;user-select:none;
|
| 23 |
+
padding-bottom:72px;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
/* ── Top Header ── */
|
| 27 |
+
.top-bar{
|
| 28 |
+
display:flex;align-items:center;justify-content:space-between;
|
| 29 |
+
padding:14px 16px 8px;
|
| 30 |
+
}
|
| 31 |
+
.top-bar .logo{font-size:18px;font-weight:800;letter-spacing:-.5px;}
|
| 32 |
+
.top-bar .logo span{color:var(--accent);}
|
| 33 |
+
.top-bar .coins{
|
| 34 |
+
background:var(--card);border:1px solid var(--border);
|
| 35 |
+
border-radius:20px;padding:6px 14px;font-size:13px;font-weight:700;
|
| 36 |
+
display:flex;align-items:center;gap:6px;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
/* ── Bottom Tab Bar ── */
|
| 40 |
+
.tab-bar{
|
| 41 |
+
position:fixed;bottom:0;left:0;right:0;
|
| 42 |
+
background:var(--card);border-top:1px solid var(--border);
|
| 43 |
+
display:flex;z-index:100;
|
| 44 |
+
padding:6px 0 env(safe-area-inset-bottom,8px);
|
| 45 |
+
}
|
| 46 |
+
.tab-bar .tab{
|
| 47 |
+
flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;
|
| 48 |
+
padding:8px 0;cursor:pointer;opacity:.45;transition:.2s;font-size:10px;font-weight:600;
|
| 49 |
+
}
|
| 50 |
+
.tab-bar .tab.active{opacity:1;color:var(--accent);}
|
| 51 |
+
.tab-bar .tab .ico{font-size:20px;}
|
| 52 |
+
|
| 53 |
+
/* ── Pages ── */
|
| 54 |
+
.page{display:none;flex-direction:column;gap:14px;padding:8px 16px 16px;flex:1;}
|
| 55 |
+
.page.active{display:flex;}
|
| 56 |
+
|
| 57 |
+
/* ── Reward Cards ── */
|
| 58 |
+
.rw-card{
|
| 59 |
+
background:var(--card);border:1px solid var(--border);
|
| 60 |
+
border-radius:var(--radius);padding:18px;
|
| 61 |
+
display:flex;align-items:center;gap:14px;
|
| 62 |
+
}
|
| 63 |
+
.rw-card .rw-icon{font-size:36px;flex-shrink:0;}
|
| 64 |
+
.rw-card .rw-body{flex:1;min-width:0;}
|
| 65 |
+
.rw-card .rw-title{font-size:15px;font-weight:700;margin-bottom:2px;}
|
| 66 |
+
.rw-card .rw-sub{font-size:12px;color:var(--dim);}
|
| 67 |
+
.rw-card .rw-amount{font-size:20px;font-weight:800;color:var(--orange);margin:4px 0;}
|
| 68 |
+
.rw-btn{
|
| 69 |
+
width:100%;padding:11px;border:none;border-radius:12px;
|
| 70 |
+
font-size:13px;font-weight:700;cursor:pointer;color:#fff;
|
| 71 |
+
transition:.15s;margin-top:6px;
|
| 72 |
+
}
|
| 73 |
+
.rw-btn.blue{background:var(--accent);}
|
| 74 |
+
.rw-btn.green{background:var(--green);}
|
| 75 |
+
.rw-btn.gray{background:#334155;color:var(--dim);cursor:default;}
|
| 76 |
+
.rw-btn:disabled{opacity:.5;cursor:not-allowed;}
|
| 77 |
+
|
| 78 |
+
/* ── Section headers ── */
|
| 79 |
+
.section-hdr{
|
| 80 |
+
font-size:14px;font-weight:700;color:var(--dim);
|
| 81 |
+
text-transform:uppercase;letter-spacing:1px;margin-top:6px;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
/* ── Leaderboard preview buttons ── */
|
| 85 |
+
.lb-btns{display:flex;flex-direction:column;gap:8px;}
|
| 86 |
+
.lb-link{
|
| 87 |
+
background:var(--card);border:1px solid var(--border);
|
| 88 |
+
border-radius:14px;padding:14px 16px;
|
| 89 |
+
display:flex;align-items:center;justify-content:space-between;
|
| 90 |
+
cursor:pointer;transition:.15s;
|
| 91 |
+
}
|
| 92 |
+
.lb-link:active{transform:scale(.98);background:var(--card2);}
|
| 93 |
+
.lb-link .ll-left{display:flex;align-items:center;gap:10px;}
|
| 94 |
+
.lb-link .ll-icon{font-size:22px;}
|
| 95 |
+
.lb-link .ll-text{font-size:14px;font-weight:600;}
|
| 96 |
+
.lb-link .ll-arrow{color:var(--dim);font-size:18px;}
|
| 97 |
+
|
| 98 |
+
/* ── Leaderboard Page ── */
|
| 99 |
+
.lb-tabs{
|
| 100 |
+
display:flex;gap:6px;padding:2px;background:var(--card);
|
| 101 |
+
border-radius:12px;border:1px solid var(--border);
|
| 102 |
+
}
|
| 103 |
+
.lb-tab{
|
| 104 |
+
flex:1;padding:9px 4px;border:none;background:none;
|
| 105 |
+
color:var(--dim);font-size:12px;font-weight:700;
|
| 106 |
+
border-radius:10px;cursor:pointer;transition:.15s;text-align:center;
|
| 107 |
+
}
|
| 108 |
+
.lb-tab.active{background:var(--accent);color:#fff;}
|
| 109 |
+
|
| 110 |
+
.lb-type-tabs{
|
| 111 |
+
display:flex;gap:6px;overflow-x:auto;padding-bottom:4px;
|
| 112 |
+
-webkit-overflow-scrolling:touch;
|
| 113 |
+
}
|
| 114 |
+
.lb-type-tabs::-webkit-scrollbar{display:none;}
|
| 115 |
+
.lb-type-btn{
|
| 116 |
+
padding:8px 14px;border:1px solid var(--border);background:var(--card);
|
| 117 |
+
color:var(--dim);font-size:12px;font-weight:600;
|
| 118 |
+
border-radius:20px;cursor:pointer;white-space:nowrap;transition:.15s;flex-shrink:0;
|
| 119 |
+
}
|
| 120 |
+
.lb-type-btn.active{background:var(--accent);color:#fff;border-color:var(--accent);}
|
| 121 |
+
|
| 122 |
+
.lb-list{display:flex;flex-direction:column;gap:4px;}
|
| 123 |
+
.lb-row{
|
| 124 |
+
display:flex;align-items:center;padding:12px 14px;
|
| 125 |
+
background:var(--card);border:1px solid var(--border);
|
| 126 |
+
border-radius:12px;gap:12px;
|
| 127 |
+
}
|
| 128 |
+
.lb-row.me{border-color:var(--accent);background:rgba(59,130,246,.08);}
|
| 129 |
+
.lb-rank{
|
| 130 |
+
width:28px;font-size:14px;font-weight:800;text-align:center;flex-shrink:0;
|
| 131 |
+
}
|
| 132 |
+
.lb-rank.gold{color:#fbbf24;} .lb-rank.silver{color:#94a3b8;} .lb-rank.bronze{color:#cd7f32;}
|
| 133 |
+
.lb-info{flex:1;min-width:0;}
|
| 134 |
+
.lb-name{font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
| 135 |
+
.lb-stat{font-size:11px;color:var(--dim);}
|
| 136 |
+
.lb-val{font-size:15px;font-weight:800;color:var(--orange);flex-shrink:0;}
|
| 137 |
+
.lb-empty{text-align:center;color:var(--dim);padding:30px 0;font-size:13px;}
|
| 138 |
+
|
| 139 |
+
/* ── Wallet Page ── */
|
| 140 |
+
.wallet-hero{
|
| 141 |
+
background:linear-gradient(135deg,#1e3a5f 0%,#0f1b2d 100%);
|
| 142 |
+
border:1px solid var(--border);border-radius:var(--radius);
|
| 143 |
+
padding:24px 20px;text-align:center;
|
| 144 |
+
}
|
| 145 |
+
.wallet-hero .wh-label{font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:1px;}
|
| 146 |
+
.wallet-hero .wh-amount{font-size:36px;font-weight:800;margin:6px 0;}
|
| 147 |
+
.wallet-hero .wh-sub{font-size:13px;color:var(--dim);}
|
| 148 |
+
.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
|
| 149 |
+
.stat-box{
|
| 150 |
+
background:var(--card);border:1px solid var(--border);
|
| 151 |
+
border-radius:14px;padding:14px;text-align:center;
|
| 152 |
+
}
|
| 153 |
+
.stat-box .sb-val{font-size:20px;font-weight:800;}
|
| 154 |
+
.stat-box .sb-label{font-size:11px;color:var(--dim);margin-top:2px;}
|
| 155 |
+
|
| 156 |
+
/* ── Reward Flow Overlay ── */
|
| 157 |
+
.overlay{
|
| 158 |
+
display:none;position:fixed;inset:0;z-index:200;
|
| 159 |
+
background:rgba(0,0,0,.85);backdrop-filter:blur(6px);
|
| 160 |
+
flex-direction:column;align-items:center;justify-content:center;padding:20px;
|
| 161 |
+
}
|
| 162 |
+
.overlay.show{display:flex;}
|
| 163 |
+
.ov-card{
|
| 164 |
+
background:var(--card);border:1px solid var(--border);
|
| 165 |
+
border-radius:20px;padding:28px 24px;text-align:center;
|
| 166 |
+
width:100%;max-width:360px;
|
| 167 |
+
}
|
| 168 |
+
.ov-card .ov-icon{font-size:52px;margin-bottom:10px;}
|
| 169 |
+
.ov-card .ov-title{font-size:18px;font-weight:700;margin-bottom:6px;}
|
| 170 |
+
.ov-card .ov-sub{font-size:13px;color:var(--dim);margin-bottom:16px;line-height:1.4;}
|
| 171 |
+
.ov-timer{font-size:48px;font-weight:800;color:var(--orange);}
|
| 172 |
+
.ov-progress{
|
| 173 |
+
width:100%;height:6px;background:rgba(255,255,255,.08);
|
| 174 |
+
border-radius:3px;margin:12px 0 16px;overflow:hidden;
|
| 175 |
+
}
|
| 176 |
+
.ov-progress-bar{height:100%;width:0%;background:var(--orange);transition:width 1s linear;}
|
| 177 |
+
.ov-close{
|
| 178 |
+
margin-top:14px;background:none;border:1px solid var(--border);
|
| 179 |
+
color:var(--dim);padding:10px 20px;border-radius:10px;
|
| 180 |
+
font-size:12px;cursor:pointer;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
@keyframes fadeIn{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:none;}}
|
| 184 |
+
.fade-in{animation:fadeIn .3s ease;}
|
| 185 |
</style>
|
| 186 |
</head>
|
| 187 |
<body>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
+
<!-- ── Top Bar ── -->
|
| 190 |
+
<div class="top-bar">
|
| 191 |
+
<div class="logo">Alexa<span>Game</span></div>
|
| 192 |
+
<div class="coins" id="hdr-coins">💰 $---</div>
|
| 193 |
+
</div>
|
| 194 |
+
|
| 195 |
+
<!-- ════════════ PAGE: REWARD ════════════ -->
|
| 196 |
+
<div class="page active" id="page-reward">
|
| 197 |
+
<!-- Daily Card -->
|
| 198 |
+
<div class="rw-card">
|
| 199 |
+
<div class="rw-icon">💎</div>
|
| 200 |
+
<div class="rw-body">
|
| 201 |
+
<div class="rw-title">Daily Reward</div>
|
| 202 |
+
<div class="rw-amount">$3,000 coins</div>
|
| 203 |
+
<button id="daily-btn" class="rw-btn blue" onclick="openDailyFlow()">🚀 Claim Now</button>
|
| 204 |
+
<div id="daily-status-text" style="font-size:11px;color:var(--dim);margin-top:4px;"></div>
|
| 205 |
</div>
|
| 206 |
</div>
|
| 207 |
|
| 208 |
+
<!-- Mission Card -->
|
| 209 |
+
<div class="rw-card">
|
| 210 |
+
<div class="rw-icon">🎯</div>
|
| 211 |
+
<div class="rw-body">
|
| 212 |
+
<div class="rw-title">Daily Ad Watch Rewards</div>
|
| 213 |
+
<div class="rw-amount">$200 × 50/day</div>
|
| 214 |
+
<button id="mission-btn" class="rw-btn blue" onclick="openMissionFlow()">🎯 Watch & Earn</button>
|
| 215 |
+
<div id="mission-status-text" style="font-size:11px;color:var(--dim);margin-top:4px;"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
</div>
|
| 217 |
</div>
|
| 218 |
|
| 219 |
+
<!-- Leaderboard Preview -->
|
| 220 |
+
<div class="section-hdr">🏆 Leaderboards</div>
|
| 221 |
+
<div class="lb-btns">
|
| 222 |
+
<div class="lb-link" onclick="goLeaderboard('bank')">
|
| 223 |
+
<div class="ll-left"><span class="ll-icon">🏦</span><span class="ll-text">Top Bank Account</span></div>
|
| 224 |
+
<span class="ll-arrow">��</span>
|
| 225 |
+
</div>
|
| 226 |
+
<div class="lb-link" onclick="goLeaderboard('wallet')">
|
| 227 |
+
<div class="ll-left"><span class="ll-icon">💰</span><span class="ll-text">Top Wallet</span></div>
|
| 228 |
+
<span class="ll-arrow">›</span>
|
| 229 |
+
</div>
|
| 230 |
+
<div class="lb-link" onclick="goLeaderboard('xp')">
|
| 231 |
+
<div class="ll-left"><span class="ll-icon">⚔️</span><span class="ll-text">Top Users (XP & Kills)</span></div>
|
| 232 |
+
<span class="ll-arrow">›</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
</div>
|
| 234 |
</div>
|
| 235 |
+
</div>
|
| 236 |
|
| 237 |
+
<!-- ════════════ PAGE: LEADERBOARD ════════════ -->
|
| 238 |
+
<div class="page" id="page-lb">
|
| 239 |
+
<div style="display:flex;align-items:center;gap:10px;margin-bottom:2px;">
|
| 240 |
+
<span style="font-size:22px;cursor:pointer;" onclick="goPage('reward')">←</span>
|
| 241 |
+
<span style="font-size:16px;font-weight:700;">Leaderboards</span>
|
| 242 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
+
<!-- Period tabs -->
|
| 245 |
+
<div class="lb-tabs">
|
| 246 |
+
<button class="lb-tab active" data-period="daily" onclick="setPeriod('daily',this)">Daily</button>
|
| 247 |
+
<button class="lb-tab" data-period="weekly" onclick="setPeriod('weekly',this)">Weekly</button>
|
| 248 |
+
<button class="lb-tab" data-period="monthly" onclick="setPeriod('monthly',this)">Month</button>
|
| 249 |
+
<button class="lb-tab" data-period="all" onclick="setPeriod('all',this)">Overall</button>
|
| 250 |
+
</div>
|
| 251 |
|
| 252 |
+
<!-- Type tabs -->
|
| 253 |
+
<div class="lb-type-tabs">
|
| 254 |
+
<button class="lb-type-btn active" data-type="wallet" onclick="setLBType('wallet',this)">💰 Wallet</button>
|
| 255 |
+
<button class="lb-type-btn" data-type="bank" onclick="setLBType('bank',this)">🏦 Bank</button>
|
| 256 |
+
<button class="lb-type-btn" data-type="xp" onclick="setLBType('xp',this)">✨ XP</button>
|
| 257 |
+
<button class="lb-type-btn" data-type="kills" onclick="setLBType('kills',this)">💀 Kills</button>
|
| 258 |
+
<button class="lb-type-btn" data-type="robs" onclick="setLBType('robs',this)">💸 Robs</button>
|
| 259 |
+
</div>
|
| 260 |
|
| 261 |
+
<div class="lb-list" id="lb-list">
|
| 262 |
+
<div class="lb-empty">Loading...</div>
|
| 263 |
+
</div>
|
| 264 |
+
</div>
|
| 265 |
+
|
| 266 |
+
<!-- ════════════ PAGE: WALLET ════════════ -->
|
| 267 |
+
<div class="page" id="page-wallet">
|
| 268 |
+
<div class="wallet-hero">
|
| 269 |
+
<div class="wh-label">Total Balance</div>
|
| 270 |
+
<div class="wh-amount" id="w-total">$0</div>
|
| 271 |
+
<div class="wh-sub" id="w-sub">Wallet + Bank</div>
|
| 272 |
+
</div>
|
| 273 |
+
<div class="stat-grid">
|
| 274 |
+
<div class="stat-box"><div class="sb-val" id="w-wallet">$0</div><div class="sb-label">Wallet</div></div>
|
| 275 |
+
<div class="stat-box"><div class="sb-val" id="w-bank">$0</div><div class="sb-label">Bank</div></div>
|
| 276 |
+
<div class="stat-box"><div class="sb-val" id="w-level">1</div><div class="sb-label">Level</div></div>
|
| 277 |
+
<div class="stat-box"><div class="sb-val" id="w-xp">0</div><div class="sb-label">XP</div></div>
|
| 278 |
+
<div class="stat-box"><div class="sb-val" id="w-kills">0</div><div class="sb-label">Kills</div></div>
|
| 279 |
+
<div class="stat-box"><div class="sb-val" id="w-robs">0</div><div class="sb-label">Robs</div></div>
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
| 282 |
+
|
| 283 |
+
<!-- ════════════ DAILY OVERLAY ════════════ -->
|
| 284 |
+
<div class="overlay" id="ov-daily">
|
| 285 |
+
<div class="ov-card">
|
| 286 |
+
<div id="dov-setup">
|
| 287 |
+
<div class="ov-icon">💎</div>
|
| 288 |
+
<div class="ov-title">Daily Reward</div>
|
| 289 |
+
<div class="ov-sub">Watch a short ad, wait 30s, then claim <strong>$3,000</strong></div>
|
| 290 |
+
<button class="rw-btn blue" id="dov-start-btn" onclick="startDaily()">🚀 Watch Ad & Claim</button>
|
| 291 |
+
</div>
|
| 292 |
+
<div id="dov-timer" style="display:none;">
|
| 293 |
+
<div class="ov-timer" id="dov-sec">30</div>
|
| 294 |
+
<div class="ov-progress"><div class="ov-progress-bar" id="dov-bar"></div></div>
|
| 295 |
+
<div class="ov-sub">Verification in progress...</div>
|
| 296 |
+
</div>
|
| 297 |
+
<div id="dov-claim" style="display:none;">
|
| 298 |
+
<button class="rw-btn green" onclick="claimDaily()">🎁 Claim $3,000 Now</button>
|
| 299 |
+
</div>
|
| 300 |
+
<div id="dov-done" style="display:none;">
|
| 301 |
+
<div class="ov-icon">✅</div>
|
| 302 |
+
<div class="ov-title">Success!</div>
|
| 303 |
+
<div class="ov-sub"><strong style="color:var(--orange);">$3,000</strong> added to wallet</div>
|
| 304 |
+
</div>
|
| 305 |
+
<div id="dov-err" style="display:none;">
|
| 306 |
+
<div class="ov-icon">❌</div>
|
| 307 |
+
<div class="ov-sub" id="dov-err-msg" style="color:var(--red);"></div>
|
| 308 |
+
</div>
|
| 309 |
+
<button class="ov-close" onclick="closeOverlay('ov-daily')">← Back</button>
|
| 310 |
+
</div>
|
| 311 |
+
</div>
|
| 312 |
+
|
| 313 |
+
<!-- ════════════ MISSION OVERLAY ════════════ -->
|
| 314 |
+
<div class="overlay" id="ov-mission">
|
| 315 |
+
<div class="ov-card">
|
| 316 |
+
<div id="mov-setup">
|
| 317 |
+
<div class="ov-icon">🎯</div>
|
| 318 |
+
<div class="ov-title">Ad Mission</div>
|
| 319 |
+
<div class="ov-sub">Watch an ad to earn <strong>$200</strong></div>
|
| 320 |
+
<div id="mov-count" style="font-size:12px;color:var(--dim);margin-bottom:10px;"></div>
|
| 321 |
+
<button class="rw-btn blue" id="mov-start-btn" onclick="startMission()">🎯 Watch Ad</button>
|
| 322 |
+
</div>
|
| 323 |
+
<div id="mov-timer" style="display:none;">
|
| 324 |
+
<div class="ov-timer" id="mov-sec">30</div>
|
| 325 |
+
<div class="ov-progress"><div class="ov-progress-bar" id="mov-bar"></div></div>
|
| 326 |
+
<div class="ov-sub">Verification in progress...</div>
|
| 327 |
+
</div>
|
| 328 |
+
<div id="mov-claim" style="display:none;">
|
| 329 |
+
<button class="rw-btn green" onclick="claimMission()">🎁 Claim $200</button>
|
| 330 |
+
</div>
|
| 331 |
+
<div id="mov-done" style="display:none;">
|
| 332 |
+
<div class="ov-icon">🏆</div>
|
| 333 |
+
<div class="ov-title">Mission Complete!</div>
|
| 334 |
+
<div class="ov-sub"><strong>$200</strong> added to wallet</div>
|
| 335 |
+
<button class="rw-btn blue" style="margin-top:12px;" onclick="resetMission()">Watch Another</button>
|
| 336 |
+
</div>
|
| 337 |
+
<div id="mov-err" style="display:none;">
|
| 338 |
+
<div class="ov-icon">❌</div>
|
| 339 |
+
<div class="ov-sub" id="mov-err-msg" style="color:var(--red);"></div>
|
| 340 |
+
</div>
|
| 341 |
+
<button class="ov-close" onclick="closeOverlay('ov-mission')">← Back</button>
|
| 342 |
+
</div>
|
| 343 |
+
</div>
|
| 344 |
|
| 345 |
+
<!-- ── Bottom Tab Bar ── -->
|
| 346 |
+
<div class="tab-bar">
|
| 347 |
+
<div class="tab active" onclick="goPage('reward')">
|
| 348 |
+
<span class="ico">🎁</span>Reward
|
| 349 |
+
</div>
|
| 350 |
+
<div class="tab" onclick="goPage('lb')">
|
| 351 |
+
<span class="ico">🏆</span>Leaders
|
| 352 |
+
</div>
|
| 353 |
+
<div class="tab" onclick="goPage('wallet')">
|
| 354 |
+
<span class="ico">👤</span>Wallet
|
| 355 |
+
</div>
|
| 356 |
+
</div>
|
| 357 |
+
|
| 358 |
+
<script>
|
| 359 |
+
const webapp = window.Telegram.WebApp;
|
| 360 |
+
webapp.expand(); webapp.ready();
|
| 361 |
+
|
| 362 |
+
if(!webapp||!webapp.initData||!webapp.initDataUnsafe||!webapp.initDataUnsafe.user||!webapp.initDataUnsafe.user.id){
|
| 363 |
+
document.body.innerHTML=`
|
| 364 |
+
<div style="padding:50px 20px;text-align:center;color:#fff;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;">
|
| 365 |
+
<div style="font-size:48px;margin-bottom:16px;">🔒</div>
|
| 366 |
+
<h2 style="margin-bottom:8px;">Access Blocked</h2>
|
| 367 |
+
<p style="color:#64748b;max-width:280px;font-size:14px;line-height:1.5;">
|
| 368 |
+
Open this app from <a href="https://t.me/Alexagamebot" style="color:#3b82f6;">@Alexagamebot</a> using <strong>/daily</strong> or <strong>/missions</strong>
|
| 369 |
+
</p>
|
| 370 |
+
</div>`;
|
| 371 |
+
throw new Error("No TG");
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
const API=window.location.origin;
|
| 375 |
+
const myId=webapp.initDataUnsafe.user.id.toString();
|
| 376 |
+
|
| 377 |
+
// ── API helper ──
|
| 378 |
+
async function api(method,path,body){
|
| 379 |
+
const o={method,headers:{'X-Init-Data':webapp.initData,'Content-Type':'application/json'}};
|
| 380 |
+
if(body)o.body=JSON.stringify(body);
|
| 381 |
+
const r=await fetch(API+path,o);
|
| 382 |
+
return r.json();
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
// ── Ad SDK ──
|
| 386 |
+
const SPOT="6120581"; const INPAGE="6120578";
|
| 387 |
+
let triggerReward=null,triggerInpage=null,triggerMission=null;
|
| 388 |
+
let missionAdsLeft=50;
|
| 389 |
+
|
| 390 |
+
window.initCdTma?.({id:SPOT}).then(s=>{triggerReward=s;triggerMission=s;}).catch(()=>{});
|
| 391 |
+
window.initCdTma?.({id:INPAGE}).then(s=>{triggerInpage=s;}).catch(()=>{});
|
| 392 |
+
|
| 393 |
+
// ── Navigation ──
|
| 394 |
+
function goPage(id){
|
| 395 |
+
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
|
| 396 |
+
document.getElementById('page-'+id).classList.add('active');
|
| 397 |
+
document.querySelectorAll('.tab-bar .tab').forEach((t,i)=>{
|
| 398 |
+
t.classList.toggle('active',['reward','lb','wallet'][i]===id);
|
| 399 |
+
});
|
| 400 |
+
if(id==='lb')loadLeaderboard();
|
| 401 |
+
if(id==='wallet')loadWallet();
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
function goLeaderboard(type){
|
| 405 |
+
goPage('lb');
|
| 406 |
+
document.querySelectorAll('.lb-type-btn').forEach(b=>b.classList.toggle('active',b.dataset.type===type));
|
| 407 |
+
currentLBType=type;
|
| 408 |
+
loadLeaderboard();
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
// ── Hub status ──
|
| 412 |
+
async function refreshHub(){
|
| 413 |
+
try{
|
| 414 |
+
const d=await api('GET','/status?type=daily');
|
| 415 |
+
const btn=document.getElementById('daily-btn');
|
| 416 |
+
const st=document.getElementById('daily-status-text');
|
| 417 |
+
if(!d.canClaim){
|
| 418 |
+
btn.className='rw-btn gray';btn.textContent='✅ Already Claimed';btn.disabled=true;
|
| 419 |
+
st.textContent='Come back tomorrow after 00:00';
|
| 420 |
+
}else{
|
| 421 |
+
btn.className='rw-btn blue';btn.textContent='🚀 Claim Now';btn.disabled=false;
|
| 422 |
+
st.textContent='Available now!';
|
| 423 |
+
}
|
| 424 |
+
}catch(e){}
|
| 425 |
+
try{
|
| 426 |
+
const m=await api('GET','/status?type=mission_inpage');
|
| 427 |
+
missionAdsLeft=m.adsLeft??50;
|
| 428 |
+
const btn=document.getElementById('mission-btn');
|
| 429 |
+
const st=document.getElementById('mission-status-text');
|
| 430 |
+
if(missionAdsLeft<=0){
|
| 431 |
+
btn.className='rw-btn gray';btn.textContent='✅ Limit Reached';btn.disabled=true;
|
| 432 |
+
st.textContent='50/50 claimed today';
|
| 433 |
+
}else{
|
| 434 |
+
btn.className='rw-btn blue';btn.textContent='🎯 Watch & Earn';btn.disabled=false;
|
| 435 |
+
st.textContent=`${50-missionAdsLeft}/50 claimed · ${missionAdsLeft} left`;
|
| 436 |
+
}
|
| 437 |
+
}catch(e){}
|
| 438 |
+
try{
|
| 439 |
+
const me=await api('GET','/me');
|
| 440 |
+
if(me.ok){
|
| 441 |
+
document.getElementById('hdr-coins').textContent='💰 $'+me.user.wallet.toLocaleString();
|
| 442 |
+
}
|
| 443 |
+
}catch(e){}
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
// ── Wallet page ──
|
| 447 |
+
async function loadWallet(){
|
| 448 |
+
try{
|
| 449 |
+
const r=await api('GET','/me');
|
| 450 |
+
if(!r.ok)return;
|
| 451 |
+
const u=r.user;
|
| 452 |
+
document.getElementById('w-total').textContent='$'+(u.wallet+u.bank).toLocaleString();
|
| 453 |
+
document.getElementById('w-wallet').textContent='$'+u.wallet.toLocaleString();
|
| 454 |
+
document.getElementById('w-bank').textContent='$'+u.bank.toLocaleString();
|
| 455 |
+
document.getElementById('w-level').textContent=u.level||1;
|
| 456 |
+
document.getElementById('w-xp').textContent=(u.xp||0).toLocaleString();
|
| 457 |
+
document.getElementById('w-kills').textContent=u.kills||0;
|
| 458 |
+
document.getElementById('w-robs').textContent=u.robs||0;
|
| 459 |
+
}catch(e){}
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
// ── Leaderboard ──
|
| 463 |
+
let currentLBType='wallet',currentLBPeriod='daily';
|
| 464 |
+
|
| 465 |
+
function setPeriod(p,el){
|
| 466 |
+
currentLBPeriod=p;
|
| 467 |
+
document.querySelectorAll('.lb-tab').forEach(t=>t.classList.remove('active'));
|
| 468 |
+
el.classList.add('active');
|
| 469 |
+
loadLeaderboard();
|
| 470 |
+
}
|
| 471 |
+
function setLBType(t,el){
|
| 472 |
+
currentLBType=t;
|
| 473 |
+
document.querySelectorAll('.lb-type-btn').forEach(b=>b.classList.remove('active'));
|
| 474 |
+
el.classList.add('active');
|
| 475 |
+
loadLeaderboard();
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
const valLabels={wallet:'Wallet',bank:'Bank',xp:'XP',kills:'Kills',robs:'Robs'};
|
| 479 |
+
const valIcons={wallet:'💰',bank:'🏦',xp:'✨',kills:'💀',robs:'💸'};
|
| 480 |
+
|
| 481 |
+
async function loadLeaderboard(){
|
| 482 |
+
const list=document.getElementById('lb-list');
|
| 483 |
+
list.innerHTML='<div class="lb-empty">Loading...</div>';
|
| 484 |
+
try{
|
| 485 |
+
const r=await api('GET',`/leaderboard?type=${currentLBType}&period=${currentLBPeriod}`);
|
| 486 |
+
if(!r.ok||!r.leaderboard.length){
|
| 487 |
+
list.innerHTML='<div class="lb-empty">No data yet for this period</div>';return;
|
| 488 |
+
}
|
| 489 |
+
list.innerHTML='';
|
| 490 |
+
r.leaderboard.forEach((u,i)=>{
|
| 491 |
+
const rank=i+1;
|
| 492 |
+
const rc=rank===1?'gold':rank===2?'silver':rank===3?'bronze':'';
|
| 493 |
+
const isMe=u.userId===myId;
|
| 494 |
+
const val=u[currentLBType]||0;
|
| 495 |
+
const prefix=currentLBType==='wallet'||currentLBType==='bank'?'$':'';
|
| 496 |
+
const sub='Lv.'+u.level+' · '+valIcons[currentLBType]+' '+(prefix)+val.toLocaleString();
|
| 497 |
+
const div=document.createElement('div');
|
| 498 |
+
div.className='lb-row fade-in'+(isMe?' me':'');
|
| 499 |
+
div.style.animationDelay=(i*30)+'ms';
|
| 500 |
+
div.innerHTML=`
|
| 501 |
+
<div class="lb-rank ${rc}">${rank<=3?['🥇','🥈','🥉'][rank-1]:rank}</div>
|
| 502 |
+
<div class="lb-info">
|
| 503 |
+
<div class="lb-name">${isMe?'⭐ ':''} @${u.username||'unknown'}</div>
|
| 504 |
+
<div class="lb-stat">${sub}</div>
|
| 505 |
+
</div>
|
| 506 |
+
<div class="lb-val">${prefix}${val.toLocaleString()}</div>`;
|
| 507 |
+
list.appendChild(div);
|
| 508 |
+
});
|
| 509 |
+
}catch(e){
|
| 510 |
+
list.innerHTML='<div class="lb-empty">Error loading leaderboard</div>';
|
| 511 |
+
}
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
// ── Overlays ──
|
| 515 |
+
function closeOverlay(id){document.getElementById(id).classList.remove('show');refreshHub();}
|
| 516 |
+
|
| 517 |
+
// ── Daily Flow ──
|
| 518 |
+
function openDailyFlow(){
|
| 519 |
+
const ov=document.getElementById('ov-daily');
|
| 520 |
+
ov.classList.add('show');
|
| 521 |
+
_show('dov-setup');_hide('dov-timer','dov-claim','dov-done','dov-err');
|
| 522 |
+
document.getElementById('dov-start-btn').disabled=false;
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
async function startDaily(){
|
| 526 |
+
if(!triggerReward){alert("Ad loading, try again.");return;}
|
| 527 |
+
const st=await api('GET','/status?type=daily');
|
| 528 |
+
if(!st.canClaim){
|
| 529 |
+
_hide('dov-setup');_show('dov-err');
|
| 530 |
+
document.getElementById('dov-err-msg').textContent='Already claimed today!';return;
|
| 531 |
+
}
|
| 532 |
+
document.getElementById('dov-start-btn').disabled=true;
|
| 533 |
+
try{
|
| 534 |
+
await triggerReward();
|
| 535 |
+
}catch(e){document.getElementById('dov-start-btn').disabled=false;return;}
|
| 536 |
+
// No 30s wait — go straight to claim after watching ad
|
| 537 |
+
_hide('dov-setup');_show('dov-claim');
|
| 538 |
+
if(triggerInpage)triggerInpage().catch(()=>{});
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
async function claimDaily(){
|
| 542 |
+
_hide('dov-claim');
|
| 543 |
+
try{
|
| 544 |
+
const r=await api('POST','/claim',{type:'daily'});
|
| 545 |
+
if(r.ok){
|
| 546 |
+
_show('dov-done');
|
| 547 |
+
if(webapp.HapticFeedback)webapp.HapticFeedback.notificationOccurred('success');
|
| 548 |
+
setTimeout(()=>closeOverlay('ov-daily'),2500);
|
| 549 |
+
}else{
|
| 550 |
+
_show('dov-err');document.getElementById('dov-err-msg').textContent=r.error||'Failed';
|
| 551 |
+
}
|
| 552 |
+
}catch(e){_show('dov-err');document.getElementById('dov-err-msg').textContent='Network error';}
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
// ── Mission Flow ──
|
| 556 |
+
function openMissionFlow(){
|
| 557 |
+
const ov=document.getElementById('ov-mission');
|
| 558 |
+
ov.classList.add('show');
|
| 559 |
+
_show('mov-setup');_hide('mov-timer','mov-claim','mov-done','mov-err');
|
| 560 |
+
document.getElementById('mov-count').textContent=`${50-missionAdsLeft}/50 claimed today`;
|
| 561 |
+
document.getElementById('mov-start-btn').disabled=!triggerMission||missionAdsLeft<=0;
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
async function startMission(){
|
| 565 |
+
if(!triggerMission){alert("Ad loading.");return;}
|
| 566 |
+
const st=await api('GET','/status?type=mission_inpage');
|
| 567 |
+
if(!st.canClaim||st.adsLeft<=0){
|
| 568 |
+
_hide('mov-setup');_show('mov-err');
|
| 569 |
+
document.getElementById('mov-err-msg').textContent='Daily limit reached!';return;
|
| 570 |
+
}
|
| 571 |
+
missionAdsLeft=st.adsLeft;
|
| 572 |
+
document.getElementById('mov-start-btn').disabled=true;
|
| 573 |
+
try{await triggerMission();}catch(e){document.getElementById('mov-start-btn').disabled=false;return;}
|
| 574 |
+
_hide('mov-setup');_show('mov-timer');
|
| 575 |
+
let t=30;
|
| 576 |
+
document.getElementById('mov-sec').textContent=t;
|
| 577 |
+
document.getElementById('mov-bar').style.width='0%';
|
| 578 |
+
const iv=setInterval(()=>{
|
| 579 |
+
t--;document.getElementById('mov-sec').textContent=t;
|
| 580 |
+
document.getElementById('mov-bar').style.width=((30-t)/30*100)+'%';
|
| 581 |
+
if(t<=0){clearInterval(iv);_hide('mov-timer');_show('mov-claim');}
|
| 582 |
+
},1000);
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
async function claimMission(){
|
| 586 |
+
_hide('mov-claim');
|
| 587 |
+
try{
|
| 588 |
+
const r=await api('POST','/claim',{type:'mission'});
|
| 589 |
+
if(r.ok){
|
| 590 |
+
missionAdsLeft=r.adsLeft??Math.max(0,missionAdsLeft-1);
|
| 591 |
+
_show('mov-done');
|
| 592 |
+
if(webapp.HapticFeedback)webapp.HapticFeedback.notificationOccurred('success');
|
| 593 |
+
}else{
|
| 594 |
+
_show('mov-err');document.getElementById('mov-err-msg').textContent=r.error||'Failed';
|
| 595 |
+
}
|
| 596 |
+
}catch(e){_show('mov-err');document.getElementById('mov-err-msg').textContent='Network error';}
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
function resetMission(){
|
| 600 |
+
_show('mov-setup');_hide('mov-done','mov-err','mov-timer','mov-claim');
|
| 601 |
+
document.getElementById('mov-count').textContent=`${50-missionAdsLeft}/50 claimed today`;
|
| 602 |
+
document.getElementById('mov-start-btn').disabled=!triggerMission||missionAdsLeft<=0;
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
// ── Helpers ──
|
| 606 |
+
function _show(...ids){ids.forEach(id=>document.getElementById(id).style.display='block');}
|
| 607 |
+
function _hide(...ids){ids.forEach(id=>document.getElementById(id).style.display='none');}
|
| 608 |
+
|
| 609 |
+
// ── Init ──
|
| 610 |
+
async function init(){
|
| 611 |
+
await refreshHub();
|
| 612 |
+
const p=new URLSearchParams(location.search).get('type');
|
| 613 |
+
if(p==='daily')setTimeout(()=>openDailyFlow(),300);
|
| 614 |
+
else if(p==='mission')setTimeout(()=>openMissionFlow(),300);
|
| 615 |
+
setInterval(refreshHub,30000);
|
| 616 |
+
}
|
| 617 |
+
init();
|
| 618 |
+
</script>
|
| 619 |
</body>
|
| 620 |
</html>
|