Spaces:
Sleeping
Sleeping
Upload 45 files
Browse files- .env.example +1 -0
- Dockerfile +19 -22
- TASKS.md +11 -0
- bot.js +86 -28
- data/shopItems.js +26 -0
- handlers/admin.js +97 -0
- handlers/combat.js +34 -64
- handlers/shop.js +158 -0
- models/User.js +15 -1
- scripts/downloadGifs.js +135 -0
- utils/chatEarn.js +35 -0
.env.example
CHANGED
|
@@ -2,6 +2,7 @@ API_ID=123456
|
|
| 2 |
API_HASH=your_api_hash
|
| 3 |
BOT_TOKEN=your_bot_token
|
| 4 |
MONGO_URI=mongodb://localhost:27017/telegram_game_bot
|
|
|
|
| 5 |
# For mini-app buttons (must be the actual HTTPS URL of the hosted app, e.g. your HF space). t.me links cause BUTTON_URL_INVALID in web app buttons.
|
| 6 |
WEBAPP_HTTPS_URL=https://huggingface.co/spaces/alexaincsl/alexagame
|
| 7 |
# (Optional) t.me link if you want to send as plain text fallback: https://t.me/Alexagamebot/alexagamereward
|
|
|
|
| 2 |
API_HASH=your_api_hash
|
| 3 |
BOT_TOKEN=your_bot_token
|
| 4 |
MONGO_URI=mongodb://localhost:27017/telegram_game_bot
|
| 5 |
+
BOT_OWNER_ID=123456789
|
| 6 |
# For mini-app buttons (must be the actual HTTPS URL of the hosted app, e.g. your HF space). t.me links cause BUTTON_URL_INVALID in web app buttons.
|
| 7 |
WEBAPP_HTTPS_URL=https://huggingface.co/spaces/alexaincsl/alexagame
|
| 8 |
# (Optional) t.me link if you want to send as plain text fallback: https://t.me/Alexagamebot/alexagamereward
|
Dockerfile
CHANGED
|
@@ -1,22 +1,19 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
COPY
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
# Start the application
|
| 22 |
-
CMD [ "npm", "start" ]
|
|
|
|
| 1 |
+
FROM node:18-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /usr/src/app
|
| 4 |
+
|
| 5 |
+
COPY package*.json ./
|
| 6 |
+
RUN npm install --production
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
ARG GIPHY_API_KEY=Ei3iNGXxdCwjwyxwvSaLq7BgNaOLPFEh
|
| 11 |
+
ENV GIPHY_API_KEY=${GIPHY_API_KEY}
|
| 12 |
+
RUN node scripts/downloadGifs.js
|
| 13 |
+
|
| 14 |
+
EXPOSE 7860
|
| 15 |
+
|
| 16 |
+
ENV PORT=7860
|
| 17 |
+
ENV NODE_ENV=production
|
| 18 |
+
|
| 19 |
+
CMD [ "npm", "start" ]
|
|
|
|
|
|
|
|
|
TASKS.md
CHANGED
|
@@ -13,6 +13,17 @@
|
|
| 13 |
- [x] Pot system (entry × 2 = winner takes all)
|
| 14 |
- [x] HTML markup for all messages
|
| 15 |
- [x] Banner + video display ads on webapp pages
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
---
|
| 18 |
|
|
|
|
| 13 |
- [x] Pot system (entry × 2 = winner takes all)
|
| 14 |
- [x] HTML markup for all messages
|
| 15 |
- [x] Banner + video display ads on webapp pages
|
| 16 |
+
- [x] Shop system (/shop, /buy, /gift with GIF messages)
|
| 17 |
+
- [x] Collection/Inventory system (/collection, reply to see other user's)
|
| 18 |
+
- [x] /wallet shows replied user's wallet when replying
|
| 19 |
+
- [x] /rob requires amount (/rob 500), shows victim's actual balance if too low
|
| 20 |
+
- [x] Auto-earn from chatting (max $1000/day, $1-15 per message)
|
| 21 |
+
- [x] Admin: /add <value> — add money to user wallet
|
| 22 |
+
- [x] Admin: /addblacklist — blacklist user from bot
|
| 23 |
+
- [x] Admin: /unblacklist — remove from blacklist
|
| 24 |
+
- [x] Admin: /blacklist — show all blacklisted users
|
| 25 |
+
- [x] Blacklisted users silently blocked from all commands + buttons
|
| 26 |
+
- [x] /coin command (renamed from /coinflip)
|
| 27 |
|
| 28 |
---
|
| 29 |
|
bot.js
CHANGED
|
@@ -6,6 +6,8 @@ const { NewMessage } = require("telegram/events");
|
|
| 6 |
const connectDB = require('./db/mongo');
|
| 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]);
|
|
@@ -22,6 +24,8 @@ const boardHandler = require('./handlers/boardHandler');
|
|
| 22 |
const blackjack = require('./handlers/blackjack');
|
| 23 |
const cardGame = require('./handlers/cardGameHandler');
|
| 24 |
const strategy = require('./handlers/strategyGames');
|
|
|
|
|
|
|
| 25 |
|
| 26 |
const apiId = parseInt(process.env.API_ID || 0);
|
| 27 |
const apiHash = process.env.API_HASH || "";
|
|
@@ -35,8 +39,6 @@ const stringSession = new StringSession("");
|
|
| 35 |
await client.start({ botAuthToken: botToken });
|
| 36 |
console.log("Bot is running...");
|
| 37 |
global._tgClient = client;
|
| 38 |
-
|
| 39 |
-
// Set default parse mode to HTML globally
|
| 40 |
client.setParseMode('html');
|
| 41 |
|
| 42 |
const isAlive = async (event) => {
|
|
@@ -52,46 +54,86 @@ const stringSession = new StringSession("");
|
|
| 52 |
return true;
|
| 53 |
};
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
client.addEventHandler(async (event) => {
|
| 56 |
const text = event.message.message || '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
if (!text) return;
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
if (text === "/start") {
|
| 60 |
await event.message.respond({ message:
|
| 61 |
"🎮 <b>Alexagame Bot</b>\n\n" +
|
| 62 |
-
"💰 /daily
|
| 63 |
-
"💰 /wallet
|
| 64 |
-
"
|
| 65 |
-
"
|
| 66 |
-
"
|
| 67 |
-
"
|
| 68 |
-
"
|
| 69 |
-
"
|
| 70 |
-
"
|
| 71 |
-
"
|
| 72 |
-
"
|
| 73 |
-
"🕹️ /
|
| 74 |
-
"
|
| 75 |
-
"
|
| 76 |
-
"
|
| 77 |
-
"
|
| 78 |
-
"🃏 /
|
| 79 |
-
"🃏 /
|
| 80 |
-
"
|
| 81 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
});
|
| 83 |
}
|
| 84 |
|
| 85 |
-
if (text === "/wallet") {
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
await event.message.respond({ message:
|
| 90 |
-
`👤 <b>
|
|
|
|
| 91 |
`💰 Wallet: <b>$${user.wallet}</b>\n` +
|
| 92 |
`💳 Bank: <b>$${user.bank}</b>\n` +
|
| 93 |
`❤️ HP: ${user.health}%\n` +
|
| 94 |
-
`💀 Kills: ${user.kills||0} | 💸 Robs: ${user.robs||0}`
|
|
|
|
| 95 |
});
|
| 96 |
}
|
| 97 |
|
|
@@ -100,14 +142,23 @@ const stringSession = new StringSession("");
|
|
| 100 |
if (text.startsWith("/dp")) await economy.deposit(client, event);
|
| 101 |
if (text.startsWith("/wd")) await economy.withdraw(client, event);
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
if (text.startsWith("/kill")) { if (await isAlive(event)) await combat.kill(client, event); }
|
| 104 |
if (text.startsWith("/rob")) { if (await isAlive(event)) await combat.rob(client, event); }
|
| 105 |
if (text === "/revive") await combat.revive(client, event);
|
| 106 |
|
|
|
|
| 107 |
if (text.startsWith("/chess")) { if (await isAlive(event)) await strategy.startChess(client, event); }
|
| 108 |
if (text.startsWith("/checkers")) { if (await isAlive(event)) await strategy.startCheckers(client, event); }
|
| 109 |
if (text === "/surrender") await strategy.surrender(client, event);
|
| 110 |
|
|
|
|
| 111 |
if (text.startsWith("/cards")) { if (await isAlive(event)) await cardGame.initCardGame(client, event); }
|
| 112 |
if (text.startsWith("/hack")) { if (await isAlive(event)) await multiHack.initHack(client, event); }
|
| 113 |
if (text === "/join") {
|
|
@@ -117,10 +168,12 @@ const stringSession = new StringSession("");
|
|
| 117 |
if (text.startsWith("/guess")) await multiHack.processGuess(client, event);
|
| 118 |
if (text.startsWith("/flip")) await cardGame.processFlip(client, event);
|
| 119 |
|
|
|
|
| 120 |
if (text.startsWith("/xox")) { if (await isAlive(event)) await boardGames.startXOX(client, event); }
|
| 121 |
if (text.startsWith("/c4")) { if (await isAlive(event)) await boardGames.startC4(client, event); }
|
| 122 |
if (text.startsWith("/dice")) { if (await isAlive(event)) await games.startDice(client, event); }
|
| 123 |
|
|
|
|
| 124 |
if (text.startsWith("/bj")) { if (await isAlive(event)) await blackjack.startBJ(client, event); }
|
| 125 |
if (text === "/slots") await inlineGames.startSlots(client, event);
|
| 126 |
if (text === "/mines") await inlineGames.startMines(client, event);
|
|
@@ -130,8 +183,13 @@ const stringSession = new StringSession("");
|
|
| 130 |
|
| 131 |
}, new NewMessage({}));
|
| 132 |
|
|
|
|
| 133 |
client.addEventHandler(async (update) => {
|
| 134 |
if (update instanceof Api.UpdateBotCallbackQuery) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
let handled = false;
|
| 136 |
const cbData = update.data.toString();
|
| 137 |
if (cbData.startsWith("cs|") || cbData.startsWith("ck|") || cbData.startsWith("csjn|") || cbData.startsWith("ckjn|") || cbData.startsWith("cssr|") || cbData.startsWith("cksr|")) {
|
|
|
|
| 6 |
const connectDB = require('./db/mongo');
|
| 7 |
const User = require('./models/User');
|
| 8 |
const { startApi } = require('./api');
|
| 9 |
+
const { processChatEarn } = require('./utils/chatEarn');
|
| 10 |
+
const { getName } = require('./utils/getName');
|
| 11 |
|
| 12 |
const requiredEnv = ['API_ID', 'API_HASH', 'BOT_TOKEN', 'MONGO_URI'];
|
| 13 |
const missing = requiredEnv.filter(k => !process.env[k]);
|
|
|
|
| 24 |
const blackjack = require('./handlers/blackjack');
|
| 25 |
const cardGame = require('./handlers/cardGameHandler');
|
| 26 |
const strategy = require('./handlers/strategyGames');
|
| 27 |
+
const shopHandler = require('./handlers/shop');
|
| 28 |
+
const admin = require('./handlers/admin');
|
| 29 |
|
| 30 |
const apiId = parseInt(process.env.API_ID || 0);
|
| 31 |
const apiHash = process.env.API_HASH || "";
|
|
|
|
| 39 |
await client.start({ botAuthToken: botToken });
|
| 40 |
console.log("Bot is running...");
|
| 41 |
global._tgClient = client;
|
|
|
|
|
|
|
| 42 |
client.setParseMode('html');
|
| 43 |
|
| 44 |
const isAlive = async (event) => {
|
|
|
|
| 54 |
return true;
|
| 55 |
};
|
| 56 |
|
| 57 |
+
const isBlacklisted = async (userId) => {
|
| 58 |
+
const user = await User.findOne({ userId });
|
| 59 |
+
return user && user.blacklisted;
|
| 60 |
+
};
|
| 61 |
+
|
| 62 |
client.addEventHandler(async (event) => {
|
| 63 |
const text = event.message.message || '';
|
| 64 |
+
const userId = event.message.senderId?.toString();
|
| 65 |
+
if (!userId) return;
|
| 66 |
+
|
| 67 |
+
// Auto-earn from chatting (all messages, not just commands)
|
| 68 |
+
if (text && !text.startsWith('/')) {
|
| 69 |
+
await processChatEarn(userId);
|
| 70 |
+
return; // non-commands don't need further processing
|
| 71 |
+
}
|
| 72 |
if (!text) return;
|
| 73 |
|
| 74 |
+
// Blacklist check for all commands
|
| 75 |
+
if (text.startsWith('/') && await isBlacklisted(userId)) {
|
| 76 |
+
return; // silently ignore blacklisted users
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
// ── Admin commands ──
|
| 80 |
+
if (text.startsWith("/add ") && !text.startsWith("/addblacklist")) await admin.addMoney(client, event);
|
| 81 |
+
if (text.startsWith("/addblacklist")) await admin.addBlacklist(client, event);
|
| 82 |
+
if (text.startsWith("/unblacklist")) await admin.unBlacklist(client, event);
|
| 83 |
+
if (text === "/blacklist") await admin.showBlacklist(client, event);
|
| 84 |
+
|
| 85 |
+
// ── General ──
|
| 86 |
if (text === "/start") {
|
| 87 |
await event.message.respond({ message:
|
| 88 |
"🎮 <b>Alexagame Bot</b>\n\n" +
|
| 89 |
+
"💰 /daily — Claim $3000 daily reward\n" +
|
| 90 |
+
"💰 /wallet — Balance & stats (reply = other user)\n" +
|
| 91 |
+
"🛒 /shop — Browse items\n" +
|
| 92 |
+
"🛍️ /buy <id> — Buy item for yourself\n" +
|
| 93 |
+
"🎁 /gift <id> — Gift item (reply to user)\n" +
|
| 94 |
+
"📦 /collection — View collection\n\n" +
|
| 95 |
+
"⚔️ /kill (reply) — Kill for cash & XP\n" +
|
| 96 |
+
"⚔️ /rob <amount> (reply) — Rob user\n" +
|
| 97 |
+
"⚔️ /revive — Revive for $1000\n\n" +
|
| 98 |
+
"♔ /chess <bet> — Chess (2P)\n" +
|
| 99 |
+
"⚪ /checkers <bet> — Checkers (2P)\n" +
|
| 100 |
+
"🕹️ /xox <bet> — Tic-Tac-Toe\n" +
|
| 101 |
+
"🕹️ /c4 <bet> — Connect 4\n" +
|
| 102 |
+
"🕹️ /dice <bet> — Dice duel\n" +
|
| 103 |
+
"🕹️ /hack <bet> <len> — PIN hack\n" +
|
| 104 |
+
"🕹️ /cards <bet> — Card flip game\n\n" +
|
| 105 |
+
"🃏 /bj <bet> — Blackjack\n" +
|
| 106 |
+
"🃏 /slots — Slot machine\n" +
|
| 107 |
+
"🃏 /mines — Avoid mines\n" +
|
| 108 |
+
"🃏 /coin — Coin flip\n" +
|
| 109 |
+
"🃏 /roulette — Russian roulette\n" +
|
| 110 |
+
"🃏 /hl — Higher or Lower\n\n" +
|
| 111 |
+
"🏳️ /surrender — Forfeit chess/checkers\n" +
|
| 112 |
+
"💬 Chat in groups to auto-earn up to $1000/day!"
|
| 113 |
});
|
| 114 |
}
|
| 115 |
|
| 116 |
+
if (text === "/wallet" || text.startsWith("/wallet ")) {
|
| 117 |
+
let targetId = userId;
|
| 118 |
+
// If reply to user, show their wallet
|
| 119 |
+
if (event.message.replyTo) {
|
| 120 |
+
try {
|
| 121 |
+
const replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 122 |
+
if (replyMsg && replyMsg[0] && replyMsg[0].senderId) targetId = replyMsg[0].senderId.toString();
|
| 123 |
+
} catch (e) {}
|
| 124 |
+
}
|
| 125 |
+
let user = await User.findOne({ userId: targetId });
|
| 126 |
+
if (!user) user = await User.create({ userId: targetId });
|
| 127 |
+
const name = await getName(client, targetId);
|
| 128 |
+
const isSelf = targetId === userId;
|
| 129 |
await event.message.respond({ message:
|
| 130 |
+
`👤 <b>${name}</b>${isSelf ? '' : "'s wallet"}\n` +
|
| 131 |
+
`🎖️ Lv.${user.level} | XP: ${user.xp}\n` +
|
| 132 |
`💰 Wallet: <b>$${user.wallet}</b>\n` +
|
| 133 |
`💳 Bank: <b>$${user.bank}</b>\n` +
|
| 134 |
`❤️ HP: ${user.health}%\n` +
|
| 135 |
+
`💀 Kills: ${user.kills||0} | 💸 Robs: ${user.robs||0}\n` +
|
| 136 |
+
`📦 Items: ${user.inventory?.length || 0}`
|
| 137 |
});
|
| 138 |
}
|
| 139 |
|
|
|
|
| 142 |
if (text.startsWith("/dp")) await economy.deposit(client, event);
|
| 143 |
if (text.startsWith("/wd")) await economy.withdraw(client, event);
|
| 144 |
|
| 145 |
+
// Shop
|
| 146 |
+
if (text === "/shop") await shopHandler.shop(client, event);
|
| 147 |
+
if (text.startsWith("/buy")) await shopHandler.buy(client, event);
|
| 148 |
+
if (text.startsWith("/gift")) await shopHandler.gift(client, event);
|
| 149 |
+
if (text === "/collection" || (text.startsWith("/collection") && event.message.replyTo)) await shopHandler.inventory(client, event);
|
| 150 |
+
|
| 151 |
+
// Combat
|
| 152 |
if (text.startsWith("/kill")) { if (await isAlive(event)) await combat.kill(client, event); }
|
| 153 |
if (text.startsWith("/rob")) { if (await isAlive(event)) await combat.rob(client, event); }
|
| 154 |
if (text === "/revive") await combat.revive(client, event);
|
| 155 |
|
| 156 |
+
// Strategy
|
| 157 |
if (text.startsWith("/chess")) { if (await isAlive(event)) await strategy.startChess(client, event); }
|
| 158 |
if (text.startsWith("/checkers")) { if (await isAlive(event)) await strategy.startCheckers(client, event); }
|
| 159 |
if (text === "/surrender") await strategy.surrender(client, event);
|
| 160 |
|
| 161 |
+
// Multiplayer lobby
|
| 162 |
if (text.startsWith("/cards")) { if (await isAlive(event)) await cardGame.initCardGame(client, event); }
|
| 163 |
if (text.startsWith("/hack")) { if (await isAlive(event)) await multiHack.initHack(client, event); }
|
| 164 |
if (text === "/join") {
|
|
|
|
| 168 |
if (text.startsWith("/guess")) await multiHack.processGuess(client, event);
|
| 169 |
if (text.startsWith("/flip")) await cardGame.processFlip(client, event);
|
| 170 |
|
| 171 |
+
// Board games
|
| 172 |
if (text.startsWith("/xox")) { if (await isAlive(event)) await boardGames.startXOX(client, event); }
|
| 173 |
if (text.startsWith("/c4")) { if (await isAlive(event)) await boardGames.startC4(client, event); }
|
| 174 |
if (text.startsWith("/dice")) { if (await isAlive(event)) await games.startDice(client, event); }
|
| 175 |
|
| 176 |
+
// Casino
|
| 177 |
if (text.startsWith("/bj")) { if (await isAlive(event)) await blackjack.startBJ(client, event); }
|
| 178 |
if (text === "/slots") await inlineGames.startSlots(client, event);
|
| 179 |
if (text === "/mines") await inlineGames.startMines(client, event);
|
|
|
|
| 183 |
|
| 184 |
}, new NewMessage({}));
|
| 185 |
|
| 186 |
+
// Callback queries
|
| 187 |
client.addEventHandler(async (update) => {
|
| 188 |
if (update instanceof Api.UpdateBotCallbackQuery) {
|
| 189 |
+
// Check blacklist for button clicks
|
| 190 |
+
const cbUserId = update.userId.toString();
|
| 191 |
+
if (await isBlacklisted(cbUserId)) return;
|
| 192 |
+
|
| 193 |
let handled = false;
|
| 194 |
const cbData = update.data.toString();
|
| 195 |
if (cbData.startsWith("cs|") || cbData.startsWith("ck|") || cbData.startsWith("csjn|") || cbData.startsWith("ckjn|") || cbData.startsWith("cssr|") || cbData.startsWith("cksr|")) {
|
data/shopItems.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Shop items catalog.
|
| 3 |
+
* gifDir: folder name under ./gifs/ containing .gif files (0.gif, 1.gif, etc.)
|
| 4 |
+
*/
|
| 5 |
+
const SHOP_ITEMS = [
|
| 6 |
+
{ id: '1', name: 'Rose', emoji: '🌹', price: 500, xpRate: 0.2, gifDir: 'rose' },
|
| 7 |
+
{ id: '2', name: 'Teddy Bear', emoji: '🧸', price: 1200, xpRate: 0.2, gifDir: 'teddy' },
|
| 8 |
+
{ id: '3', name: 'Diamond Ring', emoji: '💍', price: 5000, xpRate: 0.2, gifDir: 'ring' },
|
| 9 |
+
{ id: '4', name: 'Crown', emoji: '👑', price: 10000, xpRate: 0.2, gifDir: 'crown' },
|
| 10 |
+
{ id: '5', name: 'Chocolate', emoji: '🍫', price: 300, xpRate: 0.2, gifDir: 'chocolate' },
|
| 11 |
+
{ id: '6', name: 'Star', emoji: '⭐', price: 800, xpRate: 0.2, gifDir: 'star' },
|
| 12 |
+
{ id: '7', name: 'Heart', emoji: '❤️', price: 600, xpRate: 0.2, gifDir: 'heart' },
|
| 13 |
+
{ id: '8', name: 'Trophy', emoji: '🏆', price: 3000, xpRate: 0.2, gifDir: 'trophy' },
|
| 14 |
+
{ id: '9', name: 'Fire', emoji: '🔥', price: 1500, xpRate: 0.2, gifDir: 'fire' },
|
| 15 |
+
{ id: '10', name: 'Rocket', emoji: '🚀', price: 2000, xpRate: 0.2, gifDir: 'rocket' },
|
| 16 |
+
];
|
| 17 |
+
|
| 18 |
+
function getItem(id) {
|
| 19 |
+
return SHOP_ITEMS.find(i => i.id === id.toString());
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
function calcXP(price, rate) {
|
| 23 |
+
return Math.floor(price * (rate || 0.2));
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
module.exports = { SHOP_ITEMS, getItem, calcXP };
|
handlers/admin.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const User = require('../models/User');
|
| 2 |
+
const { getName } = require('../utils/getName');
|
| 3 |
+
|
| 4 |
+
// Bot owner ID — set in .env as BOT_OWNER_ID
|
| 5 |
+
function isOwner(userId) {
|
| 6 |
+
const ownerId = process.env.BOT_OWNER_ID;
|
| 7 |
+
return ownerId && userId === ownerId;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
// ── /add <value> (reply to user or /add <value> <userId>) ──
|
| 11 |
+
const addMoney = async (client, event) => {
|
| 12 |
+
const userId = event.message.senderId.toString();
|
| 13 |
+
if (!isOwner(userId)) return;
|
| 14 |
+
|
| 15 |
+
const parts = event.message.message.split(" ");
|
| 16 |
+
const amount = parseInt(parts[1]);
|
| 17 |
+
if (isNaN(amount) || amount <= 0) return event.message.respond({ message: "Usage: /add <amount> (reply to user)" });
|
| 18 |
+
|
| 19 |
+
let targetId = parts[2]; // /add 500 userId
|
| 20 |
+
if (!targetId && event.message.replyTo) {
|
| 21 |
+
try {
|
| 22 |
+
const replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 23 |
+
if (replyMsg && replyMsg[0] && replyMsg[0].senderId) targetId = replyMsg[0].senderId.toString();
|
| 24 |
+
} catch (e) {}
|
| 25 |
+
}
|
| 26 |
+
if (!targetId) return event.message.respond({ message: "Reply to a user or provide user ID: /add <amount> <userId>" });
|
| 27 |
+
|
| 28 |
+
let user = await User.findOne({ userId: targetId }) || await User.create({ userId: targetId });
|
| 29 |
+
user.wallet += amount;
|
| 30 |
+
await user.save();
|
| 31 |
+
|
| 32 |
+
const name = await getName(client, targetId);
|
| 33 |
+
await event.message.respond({ message: `✅ Added <b>$${amount}</b> to ${name}'s wallet. New balance: <b>$${user.wallet}</b>` });
|
| 34 |
+
};
|
| 35 |
+
|
| 36 |
+
// ── /addblacklist (reply or /addblacklist <userId>) ──
|
| 37 |
+
const addBlacklist = async (client, event) => {
|
| 38 |
+
const userId = event.message.senderId.toString();
|
| 39 |
+
if (!isOwner(userId)) return;
|
| 40 |
+
|
| 41 |
+
let targetId = event.message.message.split(" ")[1];
|
| 42 |
+
if (!targetId && event.message.replyTo) {
|
| 43 |
+
try {
|
| 44 |
+
const replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 45 |
+
if (replyMsg && replyMsg[0] && replyMsg[0].senderId) targetId = replyMsg[0].senderId.toString();
|
| 46 |
+
} catch (e) {}
|
| 47 |
+
}
|
| 48 |
+
if (!targetId) return event.message.respond({ message: "Reply to a user or: /addblacklist <userId>" });
|
| 49 |
+
|
| 50 |
+
let user = await User.findOne({ userId: targetId }) || await User.create({ userId: targetId });
|
| 51 |
+
user.blacklisted = true;
|
| 52 |
+
await user.save();
|
| 53 |
+
|
| 54 |
+
const name = await getName(client, targetId);
|
| 55 |
+
await event.message.respond({ message: `🚫 <b>${name}</b> (${targetId}) has been blacklisted.` });
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
+
// ── /unblacklist ──
|
| 59 |
+
const unBlacklist = async (client, event) => {
|
| 60 |
+
const userId = event.message.senderId.toString();
|
| 61 |
+
if (!isOwner(userId)) return;
|
| 62 |
+
|
| 63 |
+
let targetId = event.message.message.split(" ")[1];
|
| 64 |
+
if (!targetId && event.message.replyTo) {
|
| 65 |
+
try {
|
| 66 |
+
const replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 67 |
+
if (replyMsg && replyMsg[0] && replyMsg[0].senderId) targetId = replyMsg[0].senderId.toString();
|
| 68 |
+
} catch (e) {}
|
| 69 |
+
}
|
| 70 |
+
if (!targetId) return event.message.respond({ message: "Reply to a user or: /unblacklist <userId>" });
|
| 71 |
+
|
| 72 |
+
let user = await User.findOne({ userId: targetId });
|
| 73 |
+
if (!user) return event.message.respond({ message: "User not found." });
|
| 74 |
+
user.blacklisted = false;
|
| 75 |
+
await user.save();
|
| 76 |
+
|
| 77 |
+
const name = await getName(client, targetId);
|
| 78 |
+
await event.message.respond({ message: `✅ <b>${name}</b> (${targetId}) removed from blacklist.` });
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
// ── /blacklist ──
|
| 82 |
+
const showBlacklist = async (client, event) => {
|
| 83 |
+
const userId = event.message.senderId.toString();
|
| 84 |
+
if (!isOwner(userId)) return;
|
| 85 |
+
|
| 86 |
+
const users = await User.find({ blacklisted: true }).select('userId username').lean();
|
| 87 |
+
if (users.length === 0) return event.message.respond({ message: "📋 Blacklist is empty." });
|
| 88 |
+
|
| 89 |
+
let msg = `🚫 <b>Blacklisted Users</b>\n\n`;
|
| 90 |
+
for (const u of users) {
|
| 91 |
+
const name = u.username || `User${u.userId.slice(-4)}`;
|
| 92 |
+
msg += `• ${name} — <code>${u.userId}</code>\n`;
|
| 93 |
+
}
|
| 94 |
+
await event.message.respond({ message: msg });
|
| 95 |
+
};
|
| 96 |
+
|
| 97 |
+
module.exports = { addMoney, addBlacklist, unBlacklist, showBlacklist, isOwner };
|
handlers/combat.js
CHANGED
|
@@ -1,33 +1,25 @@
|
|
| 1 |
const User = require('../models/User');
|
| 2 |
-
|
| 3 |
const leveling = require('../utils/leveling');
|
|
|
|
| 4 |
|
| 5 |
const kill = async (client, event) => {
|
| 6 |
if (!event.message.replyTo) return event.message.respond({ message: "Reply to someone to kill them!" });
|
| 7 |
-
|
| 8 |
const killerId = event.message.senderId.toString();
|
| 9 |
let replyMsg;
|
| 10 |
try {
|
| 11 |
replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 12 |
-
} catch (e) {
|
| 13 |
-
|
| 14 |
-
}
|
| 15 |
-
if (!replyMsg || !replyMsg[0] || !replyMsg[0].senderId) {
|
| 16 |
return event.message.respond({ message: "You can't kill bots or channels! Reply to a real user." });
|
| 17 |
-
}
|
| 18 |
-
const victimId = replyMsg[0].senderId.toString();
|
| 19 |
|
|
|
|
| 20 |
if (killerId === victimId) return event.message.respond({ message: "You can't kill yourself!" });
|
| 21 |
|
| 22 |
-
let killer = await User.findOne({ userId: killerId });
|
| 23 |
-
let victim = await User.findOne({ userId: victimId });
|
| 24 |
-
|
| 25 |
-
if (!killer) killer = await User.create({ userId: killerId });
|
| 26 |
-
if (!victim) victim = await User.create({ userId: victimId });
|
| 27 |
-
|
| 28 |
if (victim.isDead) return event.message.respond({ message: "They are already dead!" });
|
| 29 |
|
| 30 |
-
// Reward for killing
|
| 31 |
const reward = Math.floor(Math.random() * 500) + 100;
|
| 32 |
killer.wallet += reward;
|
| 33 |
killer.kills = (killer.kills || 0) + 1;
|
|
@@ -37,19 +29,11 @@ const kill = async (client, event) => {
|
|
| 37 |
victim.health = 0;
|
| 38 |
|
| 39 |
const xpRes = await leveling.addXP(killerId, 100);
|
|
|
|
|
|
|
| 40 |
|
| 41 |
-
//
|
| 42 |
-
|
| 43 |
-
let victimName = victimId;
|
| 44 |
-
try {
|
| 45 |
-
const kEnt = await client.getEntity(killerId);
|
| 46 |
-
killerName = kEnt.firstName || kEnt.username || killerId;
|
| 47 |
-
const vEnt = await client.getEntity(victimId);
|
| 48 |
-
victimName = vEnt.firstName || vEnt.username || victimId;
|
| 49 |
-
} catch (e) {}
|
| 50 |
-
|
| 51 |
-
let msg = `💀 ${killerName} killed ${victimName}! You gained $${reward} and 100 XP.`;
|
| 52 |
-
if (xpRes.leveledUp) msg += `\n🆙 <b>LEVEL UP!</b> You are now level ${xpRes.level}!`;
|
| 53 |
|
| 54 |
await killer.save();
|
| 55 |
await victim.save();
|
|
@@ -57,54 +41,44 @@ const kill = async (client, event) => {
|
|
| 57 |
};
|
| 58 |
|
| 59 |
const rob = async (client, event) => {
|
| 60 |
-
if (!event.message.replyTo) return event.message.respond({ message: "Reply to someone to rob them!" });
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
const robberId = event.message.senderId.toString();
|
| 63 |
let replyMsg;
|
| 64 |
try {
|
| 65 |
replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 66 |
-
} catch (e) {
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
if (!replyMsg || !replyMsg[0] || !replyMsg[0].senderId) {
|
| 70 |
-
return event.message.respond({ message: "You can't kill bots or channels! Reply to a real user." });
|
| 71 |
-
}
|
| 72 |
-
const victimId = replyMsg[0].senderId.toString();
|
| 73 |
|
|
|
|
| 74 |
if (robberId === victimId) return event.message.respond({ message: "You can't rob yourself!" });
|
| 75 |
|
| 76 |
-
let robber = await User.findOne({ userId: robberId });
|
| 77 |
-
let victim = await User.findOne({ userId: victimId });
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
| 81 |
|
| 82 |
-
if (victim.wallet <= 0) return event.message.respond({ message:
|
|
|
|
| 83 |
|
| 84 |
const success = Math.random() < 0.4;
|
| 85 |
-
|
| 86 |
if (success) {
|
| 87 |
-
|
| 88 |
-
robber.wallet += stolen;
|
| 89 |
robber.robs = (robber.robs || 0) + 1;
|
| 90 |
-
robber.totalEarned = (robber.totalEarned || 0) +
|
| 91 |
-
victim.wallet -=
|
| 92 |
-
|
| 93 |
const xpRes = await leveling.addXP(robberId, 50);
|
|
|
|
|
|
|
| 94 |
|
| 95 |
-
// Get nice names
|
| 96 |
-
let robberName = robberId;
|
| 97 |
-
let victimName = victimId;
|
| 98 |
-
try {
|
| 99 |
-
const rEnt = await client.getEntity(robberId);
|
| 100 |
-
robberName = rEnt.firstName || rEnt.username || robberId;
|
| 101 |
-
const vEnt = await client.getEntity(victimId);
|
| 102 |
-
victimName = vEnt.firstName || vEnt.username || victimId;
|
| 103 |
-
} catch (e) {}
|
| 104 |
-
|
| 105 |
-
let msg = `💸 <b>Robbery Success!</b>\n${robberName} snatched $${stolen} from ${victimName} and 50 XP!`;
|
| 106 |
-
if (xpRes.leveledUp) msg += `\n🆙 <b>LEVEL UP!</b> You are now level ${xpRes.level}!`;
|
| 107 |
-
|
| 108 |
await robber.save();
|
| 109 |
await victim.save();
|
| 110 |
await event.message.respond({ message: msg });
|
|
@@ -125,17 +99,13 @@ const rob = async (client, event) => {
|
|
| 125 |
const revive = async (client, event) => {
|
| 126 |
const userId = event.message.senderId.toString();
|
| 127 |
let user = await User.findOne({ userId });
|
| 128 |
-
|
| 129 |
if (!user || !user.isDead) return event.message.respond({ message: "You are not dead!" });
|
| 130 |
-
|
| 131 |
const cost = 1000;
|
| 132 |
if (user.wallet < cost) return event.message.respond({ message: `You need $${cost} in your wallet to revive!` });
|
| 133 |
-
|
| 134 |
user.wallet -= cost;
|
| 135 |
user.isDead = false;
|
| 136 |
user.health = 100;
|
| 137 |
await user.save();
|
| 138 |
-
|
| 139 |
await event.message.respond({ message: "💖 You have been revived! $1000 deducted from wallet." });
|
| 140 |
};
|
| 141 |
|
|
|
|
| 1 |
const User = require('../models/User');
|
|
|
|
| 2 |
const leveling = require('../utils/leveling');
|
| 3 |
+
const { getName } = require('../utils/getName');
|
| 4 |
|
| 5 |
const kill = async (client, event) => {
|
| 6 |
if (!event.message.replyTo) return event.message.respond({ message: "Reply to someone to kill them!" });
|
| 7 |
+
|
| 8 |
const killerId = event.message.senderId.toString();
|
| 9 |
let replyMsg;
|
| 10 |
try {
|
| 11 |
replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 12 |
+
} catch (e) { return event.message.respond({ message: "Could not fetch the replied message." }); }
|
| 13 |
+
if (!replyMsg || !replyMsg[0] || !replyMsg[0].senderId)
|
|
|
|
|
|
|
| 14 |
return event.message.respond({ message: "You can't kill bots or channels! Reply to a real user." });
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
const victimId = replyMsg[0].senderId.toString();
|
| 17 |
if (killerId === victimId) return event.message.respond({ message: "You can't kill yourself!" });
|
| 18 |
|
| 19 |
+
let killer = await User.findOne({ userId: killerId }) || await User.create({ userId: killerId });
|
| 20 |
+
let victim = await User.findOne({ userId: victimId }) || await User.create({ userId: victimId });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
if (victim.isDead) return event.message.respond({ message: "They are already dead!" });
|
| 22 |
|
|
|
|
| 23 |
const reward = Math.floor(Math.random() * 500) + 100;
|
| 24 |
killer.wallet += reward;
|
| 25 |
killer.kills = (killer.kills || 0) + 1;
|
|
|
|
| 29 |
victim.health = 0;
|
| 30 |
|
| 31 |
const xpRes = await leveling.addXP(killerId, 100);
|
| 32 |
+
const killerName = await getName(client, killerId);
|
| 33 |
+
const victimName = await getName(client, victimId);
|
| 34 |
|
| 35 |
+
let msg = `💀 <b>${killerName}</b> killed <b>${victimName}</b>! Gained $${reward} and 100 XP.`;
|
| 36 |
+
if (xpRes.leveledUp) msg += `\n🆙 <b>LEVEL UP!</b> Level ${xpRes.level}!`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
await killer.save();
|
| 39 |
await victim.save();
|
|
|
|
| 41 |
};
|
| 42 |
|
| 43 |
const rob = async (client, event) => {
|
| 44 |
+
if (!event.message.replyTo) return event.message.respond({ message: "Reply to someone to rob them!\nUsage: /rob <amount>" });
|
| 45 |
+
|
| 46 |
+
const parts = event.message.message.split(" ");
|
| 47 |
+
const amount = parseInt(parts[1]);
|
| 48 |
+
if (isNaN(amount) || amount <= 0) return event.message.respond({ message: "Usage: /rob <amount> (reply to user)\nExample: /rob 500" });
|
| 49 |
+
|
| 50 |
const robberId = event.message.senderId.toString();
|
| 51 |
let replyMsg;
|
| 52 |
try {
|
| 53 |
replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 54 |
+
} catch (e) { return event.message.respond({ message: "Could not fetch the replied message." }); }
|
| 55 |
+
if (!replyMsg || !replyMsg[0] || !replyMsg[0].senderId)
|
| 56 |
+
return event.message.respond({ message: "You can't rob bots or channels!" });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
+
const victimId = replyMsg[0].senderId.toString();
|
| 59 |
if (robberId === victimId) return event.message.respond({ message: "You can't rob yourself!" });
|
| 60 |
|
| 61 |
+
let robber = await User.findOne({ userId: robberId }) || await User.create({ userId: robberId });
|
| 62 |
+
let victim = await User.findOne({ userId: victimId }) || await User.create({ userId: victimId });
|
| 63 |
|
| 64 |
+
const victimName = await getName(client, victimId);
|
| 65 |
+
const robberName = await getName(client, robberId);
|
| 66 |
|
| 67 |
+
if (victim.wallet <= 0) return event.message.respond({ message: `${victimName} has no money in their wallet to rob!` });
|
| 68 |
+
if (victim.wallet < amount) return event.message.respond({ message: `${victimName} only has <b>$${victim.wallet}</b> in their wallet!` });
|
| 69 |
|
| 70 |
const success = Math.random() < 0.4;
|
| 71 |
+
|
| 72 |
if (success) {
|
| 73 |
+
robber.wallet += amount;
|
|
|
|
| 74 |
robber.robs = (robber.robs || 0) + 1;
|
| 75 |
+
robber.totalEarned = (robber.totalEarned || 0) + amount;
|
| 76 |
+
victim.wallet -= amount;
|
| 77 |
+
|
| 78 |
const xpRes = await leveling.addXP(robberId, 50);
|
| 79 |
+
let msg = `💸 <b>Robbery Success!</b>\n${robberName} snatched <b>$${amount}</b> from ${victimName}! +50 XP`;
|
| 80 |
+
if (xpRes.leveledUp) msg += `\n🆙 <b>LEVEL UP!</b> Level ${xpRes.level}!`;
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
await robber.save();
|
| 83 |
await victim.save();
|
| 84 |
await event.message.respond({ message: msg });
|
|
|
|
| 99 |
const revive = async (client, event) => {
|
| 100 |
const userId = event.message.senderId.toString();
|
| 101 |
let user = await User.findOne({ userId });
|
|
|
|
| 102 |
if (!user || !user.isDead) return event.message.respond({ message: "You are not dead!" });
|
|
|
|
| 103 |
const cost = 1000;
|
| 104 |
if (user.wallet < cost) return event.message.respond({ message: `You need $${cost} in your wallet to revive!` });
|
|
|
|
| 105 |
user.wallet -= cost;
|
| 106 |
user.isDead = false;
|
| 107 |
user.health = 100;
|
| 108 |
await user.save();
|
|
|
|
| 109 |
await event.message.respond({ message: "💖 You have been revived! $1000 deducted from wallet." });
|
| 110 |
};
|
| 111 |
|
handlers/shop.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fs = require('fs');
|
| 2 |
+
const path = require('path');
|
| 3 |
+
const { Api } = require("telegram");
|
| 4 |
+
const User = require('../models/User');
|
| 5 |
+
const { SHOP_ITEMS, getItem, calcXP } = require('../data/shopItems');
|
| 6 |
+
const leveling = require('../utils/leveling');
|
| 7 |
+
const { getName } = require('../utils/getName');
|
| 8 |
+
|
| 9 |
+
// ── Pick random gif from ./gifs/<dir>/ ──
|
| 10 |
+
function getRandomGif(gifDir) {
|
| 11 |
+
const dir = path.join(__dirname, '..', 'gifs', gifDir);
|
| 12 |
+
try {
|
| 13 |
+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.gif'));
|
| 14 |
+
if (files.length === 0) return null;
|
| 15 |
+
return path.join(dir, files[Math.floor(Math.random() * files.length)]);
|
| 16 |
+
} catch (e) { return null; }
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
// ── /shop ──
|
| 20 |
+
const shop = async (client, event) => {
|
| 21 |
+
let msg = `🛒 <b>SHOP</b>\n\n`;
|
| 22 |
+
msg += `<code>ID | Item | Price</code>\n`;
|
| 23 |
+
msg += `<code>----|----------------|--------</code>\n`;
|
| 24 |
+
for (const item of SHOP_ITEMS) {
|
| 25 |
+
const id = item.id.padEnd(3);
|
| 26 |
+
const name = `${item.emoji} ${item.name}`.padEnd(16);
|
| 27 |
+
msg += `<code>${id} | ${name}| $${item.price}</code>\n`;
|
| 28 |
+
}
|
| 29 |
+
msg += `\n💰 /buy <id> — buy for yourself`;
|
| 30 |
+
msg += `\n🎁 /gift <id> — gift to someone (reply to user)`;
|
| 31 |
+
msg += `\n📦 /collection — view your inventory`;
|
| 32 |
+
await event.message.respond({ message: msg });
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
// ── /buy <id> ──
|
| 36 |
+
const buy = async (client, event) => {
|
| 37 |
+
const userId = event.message.senderId.toString();
|
| 38 |
+
const itemId = event.message.message.split(" ")[1];
|
| 39 |
+
if (!itemId) return event.message.respond({ message: "Usage: /buy <item id>\nSee /shop for item list." });
|
| 40 |
+
|
| 41 |
+
const item = getItem(itemId);
|
| 42 |
+
if (!item) return event.message.respond({ message: "❌ Item not found! Use /shop to see items." });
|
| 43 |
+
|
| 44 |
+
let user = await User.findOne({ userId }) || await User.create({ userId });
|
| 45 |
+
if (user.wallet < item.price) return event.message.respond({ message: `❌ Not enough money! Need <b>$${item.price}</b>, you have $${user.wallet}.` });
|
| 46 |
+
|
| 47 |
+
const buyerName = await getName(client, userId);
|
| 48 |
+
|
| 49 |
+
user.wallet -= item.price;
|
| 50 |
+
const xpGain = calcXP(item.price);
|
| 51 |
+
user.inventory.push({
|
| 52 |
+
itemId: item.id,
|
| 53 |
+
itemName: item.name,
|
| 54 |
+
from: 'self',
|
| 55 |
+
fromName: buyerName,
|
| 56 |
+
});
|
| 57 |
+
await user.save();
|
| 58 |
+
await leveling.addXP(userId, xpGain);
|
| 59 |
+
|
| 60 |
+
const msg = `${item.emoji} <b>${buyerName}</b> bought a <b>${item.name}</b>!\n\n💰 -$${item.price} | ✨ +${xpGain} XP`;
|
| 61 |
+
|
| 62 |
+
const gif = getRandomGif(item.gifDir);
|
| 63 |
+
if (gif) {
|
| 64 |
+
try {
|
| 65 |
+
await client.sendMessage(event.chatId, { message: msg, file: gif });
|
| 66 |
+
return;
|
| 67 |
+
} catch (e) { console.error('[shop] gif send failed:', e.message); }
|
| 68 |
+
}
|
| 69 |
+
await event.message.respond({ message: msg });
|
| 70 |
+
};
|
| 71 |
+
|
| 72 |
+
// ── /gift <id> (reply to user) ──
|
| 73 |
+
const gift = async (client, event) => {
|
| 74 |
+
if (!event.message.replyTo) return event.message.respond({ message: "Reply to someone to gift them an item!\nUsage: /gift <item id>" });
|
| 75 |
+
|
| 76 |
+
const gifterId = event.message.senderId.toString();
|
| 77 |
+
const itemId = event.message.message.split(" ")[1];
|
| 78 |
+
if (!itemId) return event.message.respond({ message: "Usage: /gift <item id>\nSee /shop for item list." });
|
| 79 |
+
|
| 80 |
+
const item = getItem(itemId);
|
| 81 |
+
if (!item) return event.message.respond({ message: "❌ Item not found! Use /shop to see items." });
|
| 82 |
+
|
| 83 |
+
let replyMsg;
|
| 84 |
+
try {
|
| 85 |
+
replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 86 |
+
} catch (e) { return event.message.respond({ message: "Could not fetch replied message." }); }
|
| 87 |
+
if (!replyMsg || !replyMsg[0] || !replyMsg[0].senderId)
|
| 88 |
+
return event.message.respond({ message: "Can't gift to bots or channels!" });
|
| 89 |
+
|
| 90 |
+
const receiverId = replyMsg[0].senderId.toString();
|
| 91 |
+
if (gifterId === receiverId) return event.message.respond({ message: "Use /buy to get items for yourself!" });
|
| 92 |
+
|
| 93 |
+
let gifter = await User.findOne({ userId: gifterId }) || await User.create({ userId: gifterId });
|
| 94 |
+
if (gifter.wallet < item.price) return event.message.respond({ message: `❌ Not enough money! Need <b>$${item.price}</b>, you have $${gifter.wallet}.` });
|
| 95 |
+
|
| 96 |
+
let receiver = await User.findOne({ userId: receiverId }) || await User.create({ userId: receiverId });
|
| 97 |
+
|
| 98 |
+
const gifterName = await getName(client, gifterId);
|
| 99 |
+
const receiverName = await getName(client, receiverId);
|
| 100 |
+
|
| 101 |
+
gifter.wallet -= item.price;
|
| 102 |
+
const xpGain = calcXP(item.price);
|
| 103 |
+
await gifter.save();
|
| 104 |
+
await leveling.addXP(gifterId, xpGain);
|
| 105 |
+
|
| 106 |
+
receiver.inventory.push({
|
| 107 |
+
itemId: item.id,
|
| 108 |
+
itemName: item.name,
|
| 109 |
+
from: gifterId,
|
| 110 |
+
fromName: gifterName,
|
| 111 |
+
});
|
| 112 |
+
await receiver.save();
|
| 113 |
+
|
| 114 |
+
const msg = `🎁 <b>${gifterName}</b> gifted a <b>${item.emoji} ${item.name}</b> to <b>${receiverName}</b>!\n\n💰 -$${item.price} | ✨ +${xpGain} XP for ${gifterName}`;
|
| 115 |
+
|
| 116 |
+
const gif = getRandomGif(item.gifDir);
|
| 117 |
+
if (gif) {
|
| 118 |
+
try {
|
| 119 |
+
await client.sendMessage(event.chatId, { message: msg, file: gif });
|
| 120 |
+
return;
|
| 121 |
+
} catch (e) { console.error('[shop] gif send failed:', e.message); }
|
| 122 |
+
}
|
| 123 |
+
await event.message.respond({ message: msg });
|
| 124 |
+
};
|
| 125 |
+
|
| 126 |
+
// ── /collection (or /collection reply to user) ──
|
| 127 |
+
const inventory = async (client, event) => {
|
| 128 |
+
let targetId = event.message.senderId.toString();
|
| 129 |
+
let targetName = await getName(client, targetId);
|
| 130 |
+
|
| 131 |
+
if (event.message.replyTo) {
|
| 132 |
+
try {
|
| 133 |
+
const replyMsg = await client.getMessages(event.chatId, { ids: [event.message.replyTo.replyToMsgId] });
|
| 134 |
+
if (replyMsg && replyMsg[0] && replyMsg[0].senderId) {
|
| 135 |
+
targetId = replyMsg[0].senderId.toString();
|
| 136 |
+
targetName = await getName(client, targetId);
|
| 137 |
+
}
|
| 138 |
+
} catch (e) {}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
let user = await User.findOne({ userId: targetId });
|
| 142 |
+
if (!user || !user.inventory || user.inventory.length === 0)
|
| 143 |
+
return event.message.respond({ message: `📦 <b>${targetName}'s Collection</b>\n\n<i>Empty — no items yet!</i>\n\nUse /shop to browse items.` });
|
| 144 |
+
|
| 145 |
+
let msg = `📦 <b>${targetName}'s Collection</b>\n\n`;
|
| 146 |
+
for (const item of user.inventory) {
|
| 147 |
+
const shopItem = getItem(item.itemId);
|
| 148 |
+
const emoji = shopItem ? shopItem.emoji : '📦';
|
| 149 |
+
if (item.from === 'self') {
|
| 150 |
+
msg += `${emoji} <b>${item.itemName}</b> — bought by themselves\n`;
|
| 151 |
+
} else {
|
| 152 |
+
msg += `${emoji} <b>${item.itemName}</b> — gifted by ${item.fromName}\n`;
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
await event.message.respond({ message: msg });
|
| 156 |
+
};
|
| 157 |
+
|
| 158 |
+
module.exports = { shop, buy, gift, inventory };
|
models/User.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
| 1 |
const mongoose = require('mongoose');
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
const UserSchema = new mongoose.Schema({
|
| 4 |
userId: { type: String, required: true, unique: true },
|
| 5 |
username: String,
|
|
@@ -17,10 +25,16 @@ const UserSchema = new mongoose.Schema({
|
|
| 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);
|
|
|
|
| 1 |
const mongoose = require('mongoose');
|
| 2 |
|
| 3 |
+
const CollectionItemSchema = new mongoose.Schema({
|
| 4 |
+
itemId: String,
|
| 5 |
+
itemName: String,
|
| 6 |
+
from: String, // 'self' or userId who gifted
|
| 7 |
+
fromName: String, // display name of gifter
|
| 8 |
+
date: { type: Date, default: Date.now },
|
| 9 |
+
}, { _id: false });
|
| 10 |
+
|
| 11 |
const UserSchema = new mongoose.Schema({
|
| 12 |
userId: { type: String, required: true, unique: true },
|
| 13 |
username: String,
|
|
|
|
| 25 |
lastKill: { type: Date, default: null },
|
| 26 |
xp: { type: Number, default: 0 },
|
| 27 |
level: { type: Number, default: 1 },
|
|
|
|
| 28 |
kills: { type: Number, default: 0 },
|
| 29 |
robs: { type: Number, default: 0 },
|
| 30 |
totalEarned: { type: Number, default: 0 },
|
| 31 |
+
// Shop & inventory
|
| 32 |
+
inventory: { type: [CollectionItemSchema], default: [] },
|
| 33 |
+
// Chat activity auto-earn
|
| 34 |
+
lastChatEarnDate: { type: String, default: null }, // 'YYYY-MM-DD'
|
| 35 |
+
chatEarnedToday: { type: Number, default: 0 },
|
| 36 |
+
// Blacklist
|
| 37 |
+
blacklisted: { type: Boolean, default: false },
|
| 38 |
});
|
| 39 |
|
| 40 |
module.exports = mongoose.model('User', UserSchema);
|
scripts/downloadGifs.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
/**
|
| 3 |
+
* Downloads GIFs from Giphy API for each shop item.
|
| 4 |
+
* Run during Docker build or manually: node scripts/downloadGifs.js
|
| 5 |
+
*
|
| 6 |
+
* Uses GIPHY_API_KEY env var or falls back to hardcoded key.
|
| 7 |
+
* Downloads 5 GIFs per item into ./gifs/<itemDir>/0.gif, 1.gif, etc.
|
| 8 |
+
*/
|
| 9 |
+
|
| 10 |
+
const https = require('https');
|
| 11 |
+
const http = require('http');
|
| 12 |
+
const fs = require('fs');
|
| 13 |
+
const path = require('path');
|
| 14 |
+
|
| 15 |
+
const API_KEY = process.env.GIPHY_API_KEY || 'Ei3iNGXxdCwjwyxwvSaLq7BgNaOLPFEh';
|
| 16 |
+
const GIFS_PER_ITEM = 5;
|
| 17 |
+
|
| 18 |
+
// Search terms mapped to each shop item gifDir
|
| 19 |
+
const SEARCH_MAP = {
|
| 20 |
+
'rose': 'rose flower',
|
| 21 |
+
'teddy': 'teddy bear cute',
|
| 22 |
+
'ring': 'diamond ring',
|
| 23 |
+
'crown': 'crown king queen',
|
| 24 |
+
'chocolate': 'chocolate candy',
|
| 25 |
+
'star': 'star gold sparkle',
|
| 26 |
+
'heart': 'heart love red',
|
| 27 |
+
'trophy': 'trophy cup winner',
|
| 28 |
+
'fire': 'fire flame',
|
| 29 |
+
'rocket': 'rocket launch',
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
const GIFS_DIR = path.join(__dirname, '..', 'gifs');
|
| 33 |
+
|
| 34 |
+
function fetch(url) {
|
| 35 |
+
return new Promise((resolve, reject) => {
|
| 36 |
+
const client = url.startsWith('https') ? https : http;
|
| 37 |
+
client.get(url, { headers: { 'User-Agent': 'AlexaGameBot/1.0' } }, (res) => {
|
| 38 |
+
// Follow redirects
|
| 39 |
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
| 40 |
+
return fetch(res.headers.location).then(resolve).catch(reject);
|
| 41 |
+
}
|
| 42 |
+
const chunks = [];
|
| 43 |
+
res.on('data', c => chunks.push(c));
|
| 44 |
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
| 45 |
+
res.on('error', reject);
|
| 46 |
+
}).on('error', reject);
|
| 47 |
+
});
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
async function searchGiphy(query, limit) {
|
| 51 |
+
const url = `https://api.giphy.com/v1/gifs/search?api_key=${API_KEY}&q=${encodeURIComponent(query)}&limit=${limit}&rating=g&lang=en`;
|
| 52 |
+
const data = await fetch(url);
|
| 53 |
+
const json = JSON.parse(data.toString());
|
| 54 |
+
if (!json.data || json.data.length === 0) {
|
| 55 |
+
console.log(` ⚠ No results for "${query}"`);
|
| 56 |
+
return [];
|
| 57 |
+
}
|
| 58 |
+
// Get the downsized GIF URLs (smaller, good for Telegram)
|
| 59 |
+
return json.data.map(g => {
|
| 60 |
+
// Prefer fixed_height_small or downsized
|
| 61 |
+
const img = g.images;
|
| 62 |
+
return img.fixed_height_small?.url || img.downsized?.url || img.original?.url;
|
| 63 |
+
}).filter(Boolean);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
async function downloadFile(url, dest) {
|
| 67 |
+
const data = await fetch(url);
|
| 68 |
+
fs.writeFileSync(dest, data);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
async function main() {
|
| 72 |
+
console.log('🎬 Downloading GIFs from Giphy...');
|
| 73 |
+
console.log(` API Key: ${API_KEY.slice(0, 8)}...`);
|
| 74 |
+
console.log(` GIFs per item: ${GIFS_PER_ITEM}`);
|
| 75 |
+
console.log(` Output: ${GIFS_DIR}\n`);
|
| 76 |
+
|
| 77 |
+
// Create base dir
|
| 78 |
+
if (!fs.existsSync(GIFS_DIR)) fs.mkdirSync(GIFS_DIR, { recursive: true });
|
| 79 |
+
|
| 80 |
+
for (const [dir, query] of Object.entries(SEARCH_MAP)) {
|
| 81 |
+
const itemDir = path.join(GIFS_DIR, dir);
|
| 82 |
+
if (!fs.existsSync(itemDir)) fs.mkdirSync(itemDir, { recursive: true });
|
| 83 |
+
|
| 84 |
+
// Skip if already has enough GIFs
|
| 85 |
+
const existing = fs.readdirSync(itemDir).filter(f => f.endsWith('.gif'));
|
| 86 |
+
if (existing.length >= GIFS_PER_ITEM) {
|
| 87 |
+
console.log(`✅ ${dir}/ — already has ${existing.length} GIFs, skipping`);
|
| 88 |
+
continue;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
console.log(`📥 ${dir}/ — searching "${query}"...`);
|
| 92 |
+
try {
|
| 93 |
+
const urls = await searchGiphy(query, GIFS_PER_ITEM);
|
| 94 |
+
let downloaded = 0;
|
| 95 |
+
for (let i = 0; i < urls.length && downloaded < GIFS_PER_ITEM; i++) {
|
| 96 |
+
const dest = path.join(itemDir, `${downloaded}.gif`);
|
| 97 |
+
try {
|
| 98 |
+
await downloadFile(urls[i], dest);
|
| 99 |
+
const size = fs.statSync(dest).size;
|
| 100 |
+
if (size < 1000) {
|
| 101 |
+
// Too small, probably an error
|
| 102 |
+
fs.unlinkSync(dest);
|
| 103 |
+
continue;
|
| 104 |
+
}
|
| 105 |
+
console.log(` ✅ ${downloaded}.gif (${(size / 1024).toFixed(1)}KB)`);
|
| 106 |
+
downloaded++;
|
| 107 |
+
} catch (e) {
|
| 108 |
+
console.log(` ❌ Failed to download: ${e.message}`);
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
if (downloaded === 0) {
|
| 112 |
+
console.log(` ⚠ No GIFs downloaded for ${dir}`);
|
| 113 |
+
}
|
| 114 |
+
} catch (e) {
|
| 115 |
+
console.log(` ❌ Search failed: ${e.message}`);
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
console.log('\n🎉 GIF download complete!');
|
| 120 |
+
|
| 121 |
+
// Summary
|
| 122 |
+
let total = 0;
|
| 123 |
+
for (const dir of Object.keys(SEARCH_MAP)) {
|
| 124 |
+
const itemDir = path.join(GIFS_DIR, dir);
|
| 125 |
+
const count = fs.existsSync(itemDir) ? fs.readdirSync(itemDir).filter(f => f.endsWith('.gif')).length : 0;
|
| 126 |
+
total += count;
|
| 127 |
+
console.log(` ${dir}: ${count} GIFs`);
|
| 128 |
+
}
|
| 129 |
+
console.log(` Total: ${total} GIFs`);
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
main().catch(e => {
|
| 133 |
+
console.error('Fatal error:', e.message);
|
| 134 |
+
process.exit(0); // Don't fail the build
|
| 135 |
+
});
|
utils/chatEarn.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Auto-earn money for chatting in groups.
|
| 3 |
+
* Max $1000 per day per user, small random amount per message.
|
| 4 |
+
*/
|
| 5 |
+
const User = require('../models/User');
|
| 6 |
+
|
| 7 |
+
const MAX_DAILY = 1000;
|
| 8 |
+
const MIN_PER_MSG = 1;
|
| 9 |
+
const MAX_PER_MSG = 15;
|
| 10 |
+
|
| 11 |
+
async function processChatEarn(userId) {
|
| 12 |
+
try {
|
| 13 |
+
let user = await User.findOne({ userId });
|
| 14 |
+
if (!user) user = await User.create({ userId });
|
| 15 |
+
if (user.blacklisted) return;
|
| 16 |
+
|
| 17 |
+
const today = new Date().toISOString().split('T')[0];
|
| 18 |
+
|
| 19 |
+
if (user.lastChatEarnDate !== today) {
|
| 20 |
+
user.lastChatEarnDate = today;
|
| 21 |
+
user.chatEarnedToday = 0;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
if (user.chatEarnedToday >= MAX_DAILY) return;
|
| 25 |
+
|
| 26 |
+
const earn = Math.floor(Math.random() * (MAX_PER_MSG - MIN_PER_MSG + 1)) + MIN_PER_MSG;
|
| 27 |
+
const actual = Math.min(earn, MAX_DAILY - user.chatEarnedToday);
|
| 28 |
+
|
| 29 |
+
user.wallet += actual;
|
| 30 |
+
user.chatEarnedToday += actual;
|
| 31 |
+
await user.save();
|
| 32 |
+
} catch (e) {}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
module.exports = { processChatEarn };
|