giftgo / bot.js
Alexainc
Fix: Restore CREDIT_PACKAGES definition in bot.js
6fe4d84
Raw
History Blame Contribute Delete
16.2 kB
require('dotenv').config();
const { Bot, webhookCallback, InlineKeyboard } = require('grammy');
const db = require('./db');
const { spin } = require('./games/slots');
const { rollDice } = require('./games/dice');
const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN);
const WEBAPP_URL = process.env.WEBAPP_URL || 'https://your-space.hf.space';
// ── Prices in Telegram Stars (XTR) ──────────────────────────────────────────
const CREDIT_PACKAGES = [
{ stars: 10, credits: 100, label: '💫 100 Credits — ⭐ 10 Stars' },
{ stars: 25, credits: 300, label: '💎 300 Credits — ⭐ 25 Stars' },
{ stars: 50, credits: 700, label: '🏆 700 Credits — ⭐ 50 Stars' },
];
// ── Native Telegram Gifts Cache ──────────────────────────────────────────
let cachedGifts = [];
async function refreshGifts() {
try {
cachedGifts = await bot.api.getAvailableGifts();
console.log(`🎁 Loaded ${cachedGifts.length} native Telegram gifts`);
} catch (e) {
console.error('❌ Failed to fetch available gifts:', e.message);
}
}
refreshGifts();
setInterval(refreshGifts, 1000 * 60 * 60); // Refresh hourly
// ── Helpers ──────────────────────────────────────────────────────────────────
function ensureUser(ctx) {
const u = ctx.from;
return db.upsertUser(u.id, u.username, u.first_name);
}
function balanceBar(balance) {
return `💰 Your balance: **${balance} credits**`;
}
// ── /start ───────────────────────────────────────────────────────────────────
bot.command('start', async (ctx) => {
const user = ensureUser(ctx);
const keyboard = new InlineKeyboard()
.webApp('🎮 Open Gift Bot', WEBAPP_URL).row()
.text('💰 Buy Credits', 'buy_credits').text('🏆 Leaderboard', 'leaderboard').row()
.text('📊 My Stats', 'my_stats').text('❓ Help', 'help');
await ctx.reply(
`🎁 *Welcome to Gift Bot, ${ctx.from.first_name}!*\n\n` +
`🎰 Spin the slots, 🎲 bet on dice, 🎁 send gifts!\n` +
`💡 Buy credits with Telegram Stars and play to win!\n\n` +
`${balanceBar(user.balance)}\n\n` +
`Use the button below to open the full Mini App 👇`,
{ parse_mode: 'Markdown', reply_markup: keyboard }
);
});
// ── /balance ────────────────────────────────────────────────────────────────
bot.command('balance', async (ctx) => {
const user = ensureUser(ctx);
await ctx.reply(
`💰 *Your Balance*\n\n` +
`Credits: **${user.balance}**\n` +
`Total Won: ${user.total_won} | Lost: ${user.total_lost} | Bets: ${user.total_bets}`,
{ parse_mode: 'Markdown' }
);
});
// ── /spin [amount] ───────────────────────────────────────────────────────────
bot.command('spin', async (ctx) => {
const user = ensureUser(ctx);
if (user.pending_prize_value) {
return ctx.reply(`🎁 You have a pending gift: **${user.pending_prize_name}**!\n\nUse the buttons below to Claim or Retry.`, {
parse_mode: 'Markdown',
reply_markup: new InlineKeyboard().text('✅ Claim', 'claim_prize').text('🔄 Retry', 'retry_prize')
});
}
const parts = ctx.message.text.trim().split(/\s+/);
const bet = parseInt(parts[1]) || 10;
if (bet < 5) return ctx.reply('❌ Minimum bet is 5 credits.');
if (user.balance < bet) return ctx.reply(`❌ Not enough credits! You have ${user.balance} credits.\nUse /buy to top up.`);
db.deductBalance(user.tg_id, bet, `Slot spin bet`);
const result = spin(bet, false);
db.recordBetWin(user.tg_id, result.isWin ? result.payout : 0, bet);
if (result.isWin) {
// Find a real gift that matches or pick a random one from cache
const targetGift = cachedGifts.find(g => g.name === result.prizeName) || cachedGifts[0];
db.setPendingPrize(user.tg_id, result.prizeName, result.payout, targetGift?.id);
const keyboard = new InlineKeyboard()
.text('🎁 Claim Native Gift', 'claim_prize')
.text('🔄 Retry (Risk it!)', 'retry_prize');
await ctx.reply(
`🎡 *SPINNING THE WHEEL...*\n\n` +
`${result.display}\n\n` +
`🎉 *WON A NATIVE GIFT: ${result.prizeName}!*\n` +
`This will be sent to your *Telegram Profile*!\n\n` +
`What would you like to do?`,
{ parse_mode: 'Markdown', reply_markup: keyboard }
);
} else {
const updated = db.getUser(user.tg_id);
await ctx.reply(
`🎰 *SPINNING...*\n\n` +
`${result.display}\n\n` +
`${result.label}\n` +
`Bet: ${bet} credits\n\n` +
`${balanceBar(updated.balance)}`,
{ parse_mode: 'Markdown' }
);
}
});
// ── /demo ───────────────────────────────────────────────────────────────────
bot.command('demo', async (ctx) => {
const result = spin(10, true);
await ctx.reply(
`🧪 *DEMO SPIN*\n\n` +
`${result.display}\n\n` +
`${result.isWin ? `🎉 (Demo) Won ${result.prizeName}!` : '😢 (Demo) No luck.'}\n\n` +
`💡 This was a free spin. Try /spin for real!`,
{ parse_mode: 'Markdown' }
);
});
// ── /bet [amount] [guess 1-6] ────────────────────────────────────────────────
bot.command('bet', async (ctx) => {
const user = ensureUser(ctx);
const parts = ctx.message.text.trim().split(/\s+/);
const bet = parseInt(parts[1]) || 10;
const guess = parseInt(parts[2]);
if (!guess || guess < 1 || guess > 6) return ctx.reply('❌ Usage: /bet <amount> <number 1-6>\nExample: /bet 20 3');
if (bet < 5) return ctx.reply('❌ Minimum bet is 5 credits.');
if (user.balance < bet) return ctx.reply(`❌ Not enough credits! You have ${user.balance}.\nUse /buy to top up.`);
db.deductBalance(user.tg_id, bet, `Dice bet`);
const result = rollDice(bet, guess);
db.recordBetWin(user.tg_id, result.payout, bet);
if (result.isWin) {
db.addBalance(user.tg_id, result.payout, `Dice win x5`);
}
const updated = db.getUser(user.tg_id);
await ctx.reply(
`🎲 *ROLLING...*\n\n` +
`You guessed: ${result.guessEmoji}\n` +
`Result: ${result.resultEmoji}\n\n` +
`${result.label}\n` +
`Bet: ${bet} credits${result.isWin ? ` → Won: **${result.payout} credits**` : ''}\n\n` +
`${balanceBar(updated.balance)}`,
{ parse_mode: 'Markdown' }
);
});
// /gift command removed as per user request (no credit gifting)
// ── /buy ─────────────────────────────────────────────────────────────────────
bot.command('buy', async (ctx) => {
ensureUser(ctx);
const keyboard = new InlineKeyboard();
CREDIT_PACKAGES.forEach((pkg, i) => {
keyboard.text(pkg.label, `buy_pkg_${i}`).row();
});
await ctx.reply('⭐ *Buy Credits with Telegram Stars*\n\nChoose a package:', {
parse_mode: 'Markdown',
reply_markup: keyboard
});
});
// ── /leaderboard ─────────────────────────────────────────────────────────────
bot.command('leaderboard', async (ctx) => {
ensureUser(ctx);
const top = db.getLeaderboard(10);
const medals = ['🥇', '🥈', '🥉', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟'];
const lines = top.map((u, i) =>
`${medals[i]} ${u.first_name || u.username || 'User'} — **${u.balance} credits**`
).join('\n');
await ctx.reply(`🏆 *Leaderboard*\n\n${lines || 'No users yet!'}`, { parse_mode: 'Markdown' });
});
// ── /help ─────────────────────────────────────────────────────────────────────
bot.command('help', async (ctx) => {
ensureUser(ctx);
await ctx.reply(
`*🎁 Gift Bot Commands*\n\n` +
`/start — Welcome screen\n` +
`/balance — Check your credits\n` +
`/spin [amount] — Spin for real Telegram Gifts\n` +
`/bet [amount] [1-6] — Dice betting\n` +
`/buy — Top up credits with Stars\n` +
`/leaderboard — Top winners\n\n` +
`💡 *Prizes:* Real native Telegram profile gifts!`,
{ parse_mode: 'Markdown' }
);
});
// ── Inline Button Callbacks ──────────────────────────────────────────────────
bot.callbackQuery('claim_prize', async (ctx) => {
const user = ensureUser(ctx);
const prize = db.claimPrize(user.tg_id);
if (!prize) return ctx.answerCallbackQuery('❌ No prize to claim');
await ctx.answerCallbackQuery(`🎁 Sending native gift...`);
try {
if (prize.gift_id) {
// NATIVE TELEGRAM GIFT SENDING
await ctx.api.sendGift(user.tg_id, prize.gift_id, "Congratulations from GiftBot! 🎆");
db.logSentGift(user.tg_id, prize.gift_id, prize.name);
await ctx.editMessageText(
`🎆 *SUCCESS! GIFT SENT!*\n\n` +
`A real **${prize.name}** has been sent to your Telegram Profile!\n` +
`Check your *Gifts* section in your profile.\n\n` +
balanceBar(db.getUser(user.tg_id).balance),
{ parse_mode: 'Markdown' }
);
} else {
// Fallback to credits if no gift_id (old record)
await ctx.editMessageText(
`✅ *Credits Claimed!*\n\n` +
`You received **${prize.val} credits**.\n\n` +
balanceBar(db.getUser(user.tg_id).balance),
{ parse_mode: 'Markdown' }
);
}
} catch (e) {
console.error('sendGift Error:', e);
// If error (e.g. PAYMENT_REQUIRED), return credits as fallback so user isn't stuck
db.addBalance(user.tg_id, prize.val || 100, "Refund: Gift sending failed");
await ctx.reply(`❌ *Gift Delivery Failed*\n\nError: ${e.message}\n\n> 💡 Note: Bot needs a Star balance and Business subscription.\nWe've credited **100 credits** to your balance as compensation.`, {
parse_mode: 'Markdown'
});
}
});
bot.callbackQuery('retry_prize', async (ctx) => {
const user = ensureUser(ctx);
if (!user.pending_prize_value) return ctx.answerCallbackQuery('❌ No prize to retry');
const oldPrizeName = user.pending_prize_name;
const originalBet = user.pending_prize_value / 10;
db.clearPendingPrize(user.tg_id);
await ctx.answerCallbackQuery('🔄 Retrying spin...');
await ctx.editMessageText(`🎰 *RE-SPINNING...*`);
const result = spin(originalBet, false);
if (result.isWin) {
db.setPendingPrize(user.tg_id, result.prizeName, result.payout);
const keyboard = new InlineKeyboard()
.text('✅ Claim Credits', 'claim_prize')
.text('🔄 Retry (Risk it!)', 'retry_prize');
await ctx.editMessageText(
`🎰 *RE-SPIN RESULT*\n\n` +
`${result.display}\n\n` +
`🎉 *WON ${result.prizeName}!*\n` +
`Value: **${result.payout} credits**\n\n` +
`Claim or Retry?`,
{ parse_mode: 'Markdown', reply_markup: keyboard }
);
} else {
const updated = db.getUser(user.tg_id);
await ctx.editMessageText(
`🎰 *RE-SPIN RESULT*\n\n` +
`${result.display}\n\n` +
`❌ Lost the retry!\n` +
`The ${oldPrizeName} is gone.\n\n` +
balanceBar(updated.balance),
{ parse_mode: 'Markdown' }
);
}
});
bot.callbackQuery('buy_credits', async (ctx) => {
await ctx.answerCallbackQuery();
const keyboard = new InlineKeyboard();
CREDIT_PACKAGES.forEach((pkg, i) => keyboard.text(pkg.label, `buy_pkg_${i}`).row());
await ctx.reply('⭐ *Choose a Credit Package:*', { parse_mode: 'Markdown', reply_markup: keyboard });
});
bot.callbackQuery('leaderboard', async (ctx) => {
await ctx.answerCallbackQuery();
const top = db.getLeaderboard(10);
const medals = ['🥇', '🥈', '🥉', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟'];
const lines = top.map((u, i) =>
`${medals[i]} ${u.first_name || u.username || 'User'} — **${u.balance}**`
).join('\n');
await ctx.reply(`🏆 *Leaderboard*\n\n${lines || 'No players yet!'}`, { parse_mode: 'Markdown' });
});
bot.callbackQuery('my_stats', async (ctx) => {
await ctx.answerCallbackQuery();
const user = ensureUser(ctx);
await ctx.reply(
`📊 *Your Stats*\n\n` +
`💰 Balance: ${user.balance} credits\n` +
`🏆 Total Won: ${user.total_won}\n` +
`💸 Total Lost: ${user.total_lost}\n` +
`🎲 Total Bets: ${user.total_bets}`,
{ parse_mode: 'Markdown' }
);
});
bot.callbackQuery('help', async (ctx) => {
await ctx.answerCallbackQuery();
await ctx.reply(
`*🎁 Gift Bot Commands*\n\n/start /balance /spin /bet /gift /buy /leaderboard /help`,
{ parse_mode: 'Markdown' }
);
});
// ── Buy Package — send Stars invoice ─────────────────────────────────────────
for (let i = 0; i < CREDIT_PACKAGES.length; i++) {
bot.callbackQuery(`buy_pkg_${i}`, async (ctx) => {
await ctx.answerCallbackQuery();
const pkg = CREDIT_PACKAGES[i];
await ctx.replyWithInvoice(
`Buy ${pkg.credits} Credits`,
`Top up your Gift Bot wallet with ${pkg.credits} credits and start playing!`,
`credits_${i}`, // payload
'XTR', // Telegram Stars currency
[{ label: `${pkg.credits} Credits`, amount: pkg.stars }]
);
});
}
// ── Pre-checkout: always approve ─────────────────────────────────────────────
bot.on('pre_checkout_query', async (ctx) => {
await ctx.answerPreCheckoutQuery(true);
});
// ── Successful payment: credit user ──────────────────────────────────────────
bot.on('message:successful_payment', async (ctx) => {
const payload = ctx.message.successful_payment.invoice_payload;
const stars = ctx.message.successful_payment.total_amount;
const user = ensureUser(ctx);
const pkgIndex = parseInt(payload.replace('credits_', ''));
const pkg = CREDIT_PACKAGES[pkgIndex];
if (pkg) {
db.addBalance(user.tg_id, pkg.credits, `Bought ${pkg.credits} credits for ${stars} Stars`);
const updated = db.getUser(user.tg_id);
await ctx.reply(
`✅ *Payment Successful!*\n\n` +
`You received **${pkg.credits} credits**!\n\n` +
`${balanceBar(updated.balance)}`,
{ parse_mode: 'Markdown' }
);
}
});
module.exports = bot;