whatsapp-chess-bot 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +82 -0
  2. package/index.js +196 -0
  3. package/package.json +28 -0
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # WhatsApp Chess Bot โ™Ÿ๏ธ
2
+
3
+ A WhatsApp bot that lets you play chess against an AI opponent using interactive polls. The bot displays the chess board as images and uses polls for move selection.
4
+
5
+ ## Features
6
+
7
+ - ๐ŸŽฎ Play chess via WhatsApp polls
8
+ - ๐Ÿค– AI opponent powered by js-chess-engine
9
+ - ๐Ÿ–ผ๏ธ Visual chess board using chessboardimage.com
10
+ - ๐Ÿงน Automatic message cleanup (keeps only last 2 board images)
11
+ - โ™Ÿ๏ธ Choose piece type, then select specific move
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install whatsapp-chess-bot
17
+ ```
18
+
19
+ Or clone and run:
20
+
21
+ ```bash
22
+ git clone <your-repo-url>
23
+ cd whatsapp-chess-bot
24
+ npm install
25
+ npm start
26
+ ```
27
+
28
+ ## Setup
29
+
30
+ 1. Run the bot:
31
+ ```bash
32
+ npm start
33
+ ```
34
+
35
+ 2. Scan the QR code with WhatsApp (Link Device)
36
+
37
+ 3. Bot is ready! Send commands in any WhatsApp chat.
38
+
39
+ ## Commands
40
+
41
+ - `@chess start` - Start a new chess game
42
+ - `@chess clean` - Delete all bot images from chat
43
+
44
+ ## How to Play
45
+
46
+ 1. Send `@chess start` in any WhatsApp chat
47
+ 2. The bot shows the current board position
48
+ 3. Vote in the "Choose a piece type" poll (Pawn, Knight, Bishop, etc.)
49
+ 4. Vote in the move poll to select your piece's destination
50
+ 5. The AI responds automatically
51
+ 6. Repeat until checkmate or stalemate
52
+
53
+ ## Configuration
54
+
55
+ Edit `index.js` to customize:
56
+
57
+ - **AI Difficulty**: Change `engine.aiMove(gameData.game.fen(), 2)` - the `2` is the search depth (higher = stronger)
58
+ - **Max Images**: Change `enforceMaxImages(chatId, 2)` - default keeps 2 board images
59
+ - **Session Name**: Change `clientId: "chess-poll-session"` for multiple instances
60
+
61
+ ## Requirements
62
+
63
+ - Node.js 14 or higher
64
+ - WhatsApp account
65
+ - Internet connection
66
+
67
+ ## Dependencies
68
+
69
+ - `whatsapp-web.js` - WhatsApp client
70
+ - `qrcode-terminal` - QR code display
71
+ - `chess.js` - Chess game logic
72
+ - `js-chess-engine` - AI opponent
73
+
74
+ ## Notes
75
+
76
+ - WhatsApp may prevent deletion of old messages depending on timing/permissions
77
+ - For best experience, enable disappearing messages in the chat
78
+ - Board images are fetched from https://chessboardimage.com/
79
+
80
+ ## License
81
+
82
+ MIT
package/index.js ADDED
@@ -0,0 +1,196 @@
1
+ const { Client, LocalAuth, MessageMedia, Poll } = require('whatsapp-web.js');
2
+ const qrcode = require('qrcode-terminal');
3
+ const { Chess } = require('chess.js');
4
+ const engine = require('js-chess-engine');
5
+
6
+ const client = new Client({
7
+ authStrategy: new LocalAuth({ clientId: "chess-poll-session" }),
8
+ webVersionCache: {
9
+ type: 'remote',
10
+ remotePath: 'https://raw.githubusercontent.com/wppconnect-team/wa-version/main/html/2.2412.54.html',
11
+ },
12
+ puppeteer: { args: ['--no-sandbox', '--disable-setuid-sandbox'] }
13
+ });
14
+
15
+ const activeChess = {};
16
+ const PIECE_NAMES = { 'p': 'Pawn โ™Ÿ๏ธ', 'n': 'Knight โ™ž', 'b': 'Bishop โ™', 'r': 'Rook โ™œ', 'q': 'Queen โ™›', 'k': 'King โ™š' };
17
+
18
+ client.on('qr', (qr) => qrcode.generate(qr, { small: true }));
19
+ client.on('ready', () => console.log('โ™Ÿ๏ธ Chess Poll Bot is online!'));
20
+
21
+ client.on('message_create', async (msg) => {
22
+ if (msg.fromMe) return;
23
+ const chatId = msg.from;
24
+ const body = msg.body.toLowerCase().trim();
25
+
26
+ if (body === '@chess clean') {
27
+ await deletePastImages(chatId);
28
+ await client.sendMessage(chatId, '๐Ÿงน Deleted recent bot images.');
29
+ return;
30
+ }
31
+
32
+ if (body === '@chess start') {
33
+ activeChess[chatId] = {
34
+ game: new Chess(),
35
+ lastPollId: null,
36
+ phase: 'piece_select',
37
+ trackedMessageIds: [],
38
+ lastPiecePollId: null,
39
+ imageMessageIds: []
40
+ };
41
+ await deletePastImages(chatId);
42
+ await client.sendMessage(chatId, "โ™Ÿ๏ธ *Chess Started!* Use the polls to play.");
43
+ await sendChessBoard(chatId);
44
+ await sendPiecePoll(chatId);
45
+ }
46
+ });
47
+
48
+ client.on('vote_update', async (vote) => {
49
+ const chatId = vote.parentMessage.to;
50
+ const gameData = activeChess[chatId];
51
+ if (!gameData || vote.parentMessage.id._serialized !== gameData.lastPollId) return;
52
+
53
+ const selection = vote.selectedOptions[0]?.name;
54
+ if (!selection) return;
55
+
56
+ if (gameData.phase === 'piece_select') {
57
+ const pieceType = Object.keys(PIECE_NAMES).find(k => PIECE_NAMES[k] === selection);
58
+ await sendMovePoll(chatId, pieceType);
59
+ } else if (gameData.phase === 'move_select') {
60
+ await executeMove(chatId, selection);
61
+ }
62
+ });
63
+
64
+ async function sendPiecePoll(chatId) {
65
+ const gameData = activeChess[chatId];
66
+ if (gameData.lastPiecePollId) {
67
+ await deleteMessageById(gameData.lastPiecePollId);
68
+ gameData.lastPiecePollId = null;
69
+ }
70
+ const moves = gameData.game.moves({ verbose: true });
71
+ const pieces = [...new Set(moves.map(m => m.piece))].map(p => PIECE_NAMES[p]);
72
+ const poll = new Poll('Choose a piece type:', pieces);
73
+ const sent = await client.sendMessage(chatId, poll);
74
+ gameData.lastPiecePollId = sent.id._serialized;
75
+ gameData.lastPollId = sent.id._serialized;
76
+ gameData.phase = 'piece_select';
77
+ }
78
+
79
+ async function sendMovePoll(chatId, pieceType) {
80
+ const gameData = activeChess[chatId];
81
+ await deleteTrackedMessages(chatId);
82
+ if (gameData.lastPiecePollId) {
83
+ await deleteMessageById(gameData.lastPiecePollId);
84
+ gameData.lastPiecePollId = null;
85
+ }
86
+ const moves = gameData.game.moves({ verbose: true }).filter(m => m.piece === pieceType).map(m => m.san);
87
+ const poll = new Poll(`Move ${PIECE_NAMES[pieceType]} to:`, moves.slice(0, 12));
88
+ const sent = await sendTrackedMessage(chatId, poll);
89
+ gameData.lastPollId = sent.id._serialized;
90
+ gameData.phase = 'move_select';
91
+ }
92
+
93
+ async function executeMove(chatId, moveSan) {
94
+ const gameData = activeChess[chatId];
95
+ await deleteTrackedMessages(chatId);
96
+ if (gameData.lastPollId) {
97
+ await deleteMessageById(gameData.lastPollId);
98
+ }
99
+ gameData.game.move(moveSan);
100
+ let computerPlayedSan = null;
101
+ if (!gameData.game.isGameOver()) {
102
+ const ai = engine.aiMove(gameData.game.fen(), 2);
103
+ const from = Object.keys(ai)[0].toLowerCase();
104
+ const to = ai[Object.keys(ai)[0]].toLowerCase();
105
+ const aiMove = gameData.game.move({ from, to, promotion: 'q' });
106
+ computerPlayedSan = aiMove?.san || `${from}-${to}`;
107
+ }
108
+ if (computerPlayedSan) {
109
+ await sendTrackedMessage(chatId, `๐Ÿค– Computer played: *${computerPlayedSan}*`);
110
+ }
111
+ await sendChessBoard(chatId);
112
+ if (gameData.game.isGameOver()) {
113
+ await client.sendMessage(chatId, "๐Ÿ *Game Over!*");
114
+ delete activeChess[chatId];
115
+ } else {
116
+ await sendPiecePoll(chatId);
117
+ }
118
+ }
119
+
120
+ async function sendChessBoard(chatId) {
121
+ const gameData = activeChess[chatId];
122
+ const fen = gameData.game.fen();
123
+ const fenUrl = encodeURI(fen); // Keep '/' separators, encode spaces
124
+ console.log(fen);
125
+ const media = await MessageMedia.fromUrl(`https://chessboardimage.com/${fenUrl}.png`);
126
+ const sent = await client.sendMessage(chatId, media);
127
+ gameData.imageMessageIds ??= [];
128
+ gameData.imageMessageIds.push(sent.id._serialized);
129
+ await enforceMaxImages(chatId, 2);
130
+ }
131
+
132
+ async function sendTrackedMessage(chatId, content, track = true) {
133
+ const sent = await client.sendMessage(chatId, content);
134
+ const gameData = activeChess[chatId];
135
+ if (gameData && track) {
136
+ gameData.trackedMessageIds ??= [];
137
+ gameData.trackedMessageIds.push(sent.id._serialized);
138
+ }
139
+ return sent;
140
+ }
141
+
142
+ async function deleteTrackedMessages(chatId) {
143
+ const gameData = activeChess[chatId];
144
+ if (!gameData?.trackedMessageIds?.length) return;
145
+
146
+ const idsToDelete = [...gameData.trackedMessageIds];
147
+ gameData.trackedMessageIds = [];
148
+
149
+ for (const id of idsToDelete) {
150
+ try {
151
+ const message = await client.getMessageById(id);
152
+ if (message) await message.delete(true);
153
+ } catch (err) {
154
+ console.log(`Could not delete message ${id}:`, err.message);
155
+ }
156
+ }
157
+ }
158
+
159
+ async function deletePastImages(chatId) {
160
+ try {
161
+ const chat = await client.getChatById(chatId);
162
+ const recent = await chat.fetchMessages({ limit: 200 });
163
+ for (const msg of recent) {
164
+ if (!msg.fromMe || !msg.hasMedia) continue;
165
+ try {
166
+ await msg.delete(true);
167
+ } catch (err) {
168
+ console.log(`Could not delete old image ${msg.id?._serialized}:`, err.message);
169
+ }
170
+ }
171
+ } catch (err) {
172
+ console.log('Could not fetch chat for image cleanup:', err.message);
173
+ }
174
+ }
175
+
176
+ async function enforceMaxImages(chatId, maxImages = 2) {
177
+ const gameData = activeChess[chatId];
178
+ if (!gameData) return;
179
+
180
+ gameData.imageMessageIds ??= [];
181
+ while (gameData.imageMessageIds.length > maxImages) {
182
+ const oldestId = gameData.imageMessageIds.shift();
183
+ if (oldestId) await deleteMessageById(oldestId);
184
+ }
185
+ }
186
+
187
+ async function deleteMessageById(messageId) {
188
+ try {
189
+ const message = await client.getMessageById(messageId);
190
+ if (message) await message.delete(true);
191
+ } catch (err) {
192
+ console.log(`Could not delete message ${messageId}:`, err.message);
193
+ }
194
+ }
195
+
196
+ client.initialize();
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "whatsapp-chess-bot",
3
+ "version": "1.0.0",
4
+ "description": "A WhatsApp bot that lets you play chess via interactive polls with an AI opponent",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "node index.js"
8
+ },
9
+ "keywords": [
10
+ "whatsapp",
11
+ "chess",
12
+ "bot",
13
+ "poll",
14
+ "game",
15
+ "ai"
16
+ ],
17
+ "author": "David Sebbag",
18
+ "license": "GPL-3.0",
19
+ "dependencies": {
20
+ "whatsapp-web.js": "^1.23.0",
21
+ "qrcode-terminal": "^0.12.0",
22
+ "chess.js": "^1.0.0",
23
+ "js-chess-engine": "^1.0.2"
24
+ },
25
+ "engines": {
26
+ "node": ">=14.0.0"
27
+ }
28
+ }