hansaka01 commited on
Commit
908a44c
·
verified ·
1 Parent(s): e777a1d

Upload 45 files

Browse files
Files changed (11) hide show
  1. .env.example +1 -0
  2. Dockerfile +19 -22
  3. TASKS.md +11 -0
  4. bot.js +86 -28
  5. data/shopItems.js +26 -0
  6. handlers/admin.js +97 -0
  7. handlers/combat.js +34 -64
  8. handlers/shop.js +158 -0
  9. models/User.js +15 -1
  10. scripts/downloadGifs.js +135 -0
  11. 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
- # Use official Node.js light image
2
- FROM node:18-slim
3
-
4
- # Create app directory
5
- WORKDIR /usr/src/app
6
-
7
- # Install dependencies first (for better caching)
8
- COPY package*.json ./
9
- RUN npm install --production
10
-
11
- # Copy app source
12
- COPY . .
13
-
14
- # Expose port (standard for Hugging Face)
15
- EXPOSE 7860
16
-
17
- # Set environment
18
- ENV PORT=7860
19
- ENV NODE_ENV=production
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 - Claim $3000 daily reward\n" +
63
- "💰 /wallet - Balance &amp; stats\n" +
64
- "⚔️ /kill (reply) - Kill for cash &amp; XP\n" +
65
- "️ /rob (reply) - Rob user (risky)\n" +
66
- "⚔️ /revive - Revive for $1000\n\n" +
67
- " /chess &lt;bet&gt; - Chess (2P)\n" +
68
- " /checkers &lt;bet&gt; - Checkers (2P)\n" +
69
- "🕹️ /xox &lt;bet&gt; - Tic-Tac-Toe\n" +
70
- "🕹️ /c4 &lt;bet&gt; - Connect 4\n" +
71
- "🕹️ /hack &lt;bet&gt; &lt;len&gt; - PIN hack\n" +
72
- "🕹️ /cards &lt;bet&gt; - Card flip game\n" +
73
- "🕹️ /dice &lt;bet&gt; - Dice duel\n\n" +
74
- "🃏 /bj &lt;bet&gt; - Blackjack\n" +
75
- "🃏 /slots - Slot machine\n" +
76
- "🃏 /mines - Avoid mines\n" +
77
- "🃏 /coin - Coin flip\n" +
78
- "🃏 /roulette - Russian roulette\n" +
79
- "🃏 /hl - Higher or Lower\n\n" +
80
- "🏳️ /surrender - Forfeit chess/checkers\n" +
81
- "Use /daily or /missions to open the reward mini app!"
 
 
 
 
82
  });
83
  }
84
 
85
- if (text === "/wallet") {
86
- const userId = event.message.senderId.toString();
87
- let user = await User.findOne({ userId });
88
- if (!user) user = await User.create({ userId, username: event.message.sender?.username });
 
 
 
 
 
 
 
 
 
89
  await event.message.respond({ message:
90
- `👤 <b>Lv.${user.level}</b> | XP: ${user.xp}\n` +
 
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 &amp; stats (reply = other user)\n" +
91
+ "🛒 /shop Browse items\n" +
92
+ "🛍️ /buy &lt;id&gt; Buy item for yourself\n" +
93
+ "🎁 /gift &lt;id&gt; Gift item (reply to user)\n" +
94
+ "📦 /collection View collection\n\n" +
95
+ "⚔️ /kill (reply) Kill for cash &amp; XP\n" +
96
+ "️ /rob &lt;amount&gt; (reply) — Rob user\n" +
97
+ "️ /revive Revive for $1000\n\n" +
98
+ " /chess &lt;bet&gt; Chess (2P)\n" +
99
+ " /checkers &lt;bet&gt; Checkers (2P)\n" +
100
+ "🕹️ /xox &lt;bet&gt; Tic-Tac-Toe\n" +
101
+ "🕹️ /c4 &lt;bet&gt; Connect 4\n" +
102
+ "🕹️ /dice &lt;bet&gt; Dice duel\n" +
103
+ "🕹️ /hack &lt;bet&gt; &lt;len&gt; — PIN hack\n" +
104
+ "🕹️ /cards &lt;bet&gt; Card flip game\n\n" +
105
+ "🃏 /bj &lt;bet&gt; 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 &lt;amount&gt; (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 &lt;amount&gt; &lt;userId&gt;" });
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 &lt;userId&gt;" });
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 &lt;userId&gt;" });
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
- return event.message.respond({ message: "Could not fetch the replied message." });
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
- // Get nice names
42
- let killerName = killerId;
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
- return event.message.respond({ message: "Could not fetch the replied message." });
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
- if (!robber) robber = await User.create({ userId: robberId });
80
- if (!victim) victim = await User.create({ userId: victimId });
81
 
82
- if (victim.wallet <= 0) return event.message.respond({ message: "They have no money in their wallet to rob!" });
 
83
 
84
  const success = Math.random() < 0.4;
85
-
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);
 
 
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 &lt;amount&gt;" });
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 &lt;amount&gt; (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 &lt;id&gt; — buy for yourself`;
30
+ msg += `\n🎁 /gift &lt;id&gt; — 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 &lt;item id&gt;\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 &lt;item id&gt;" });
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 &lt;item id&gt;\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 };