Mohammed Foud
commited on
Commit
·
ebccf84
1
Parent(s):
d7f80c2
first commit
Browse files- .~lock.AI_telecom.xlsx# +1 -0
- AI_telecom.xlsx +0 -0
- app.js +0 -272
- e.py +143 -0
- number_statuses.csv +0 -0
- old/app.js +305 -0
- app.py → old/app.py +0 -0
- contacts.csv → old/contacts.csv +0 -0
- d.py → old/d.py +0 -0
- google_contacts.csv → old/google_contacts.csv +0 -0
- old/group_state.json +6 -0
- history.txt → old/history.txt +0 -0
- index.js → old/index.js +0 -0
- old/mapp.js +422 -0
- old/number_statuses.csv +1 -0
- number_statuses1.csv → old/number_statuses1.csv +0 -0
- old/numbers.xlsx +0 -0
- old/qrcode.png +0 -0
- package.json +1 -0
- pnpm-lock.yaml +148 -0
- r.sh +2 -2
- run.sh +1 -1
- src/AccountManager.js +309 -0
- src/config.js +13 -0
- src/index.js +17 -0
- src/numbers.xlsx +0 -0
- src/routes.js +321 -0
- src/shared.js +3 -0
- src/shared_state.json +9 -0
- src/shared_statuses.csv +7 -0
- src/views/index.html +163 -0
.~lock.AI_telecom.xlsx#
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
,mfoud444,mfoud444,09.02.2025 02:56,file:///home/mfoud444/.config/libreoffice/4;
|
AI_telecom.xlsx
ADDED
Binary file (12 kB). View file
|
|
app.js
DELETED
@@ -1,272 +0,0 @@
|
|
1 |
-
const { Client, LocalAuth } = require('whatsapp-web.js');
|
2 |
-
const qrcode = require('qrcode-terminal');
|
3 |
-
const xlsx = require('xlsx');
|
4 |
-
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
|
5 |
-
const fs = require('fs');
|
6 |
-
const http = require('http'); // Add HTTP server
|
7 |
-
|
8 |
-
const path = require('path');
|
9 |
-
const client = new Client({
|
10 |
-
authStrategy: new LocalAuth(),
|
11 |
-
puppeteer: {
|
12 |
-
headless: true,
|
13 |
-
defaultViewport: null,
|
14 |
-
executablePath: '/usr/bin/google-chrome',
|
15 |
-
args: ['--no-sandbox'],}
|
16 |
-
});
|
17 |
-
|
18 |
-
const STATE_FILE = 'group_state.json';
|
19 |
-
const MAX_GROUP_SIZE = 230;
|
20 |
-
const DAILY_BATCH_SIZE = 1;
|
21 |
-
|
22 |
-
// CSV Writer configuration
|
23 |
-
const csvWriter = createCsvWriter({
|
24 |
-
path: 'number_statuses.csv',
|
25 |
-
header: [
|
26 |
-
{ id: 'phone', title: 'PHONE' },
|
27 |
-
{ id: 'name', title: 'NAME' },
|
28 |
-
{ id: 'status', title: 'STATUS' },
|
29 |
-
{ id: 'error_code', title: 'ERROR_CODE' },
|
30 |
-
{ id: 'message', title: 'MESSAGE' },
|
31 |
-
{ id: 'is_invite_sent', title: 'INVITE_SENT' }
|
32 |
-
],
|
33 |
-
append: true
|
34 |
-
});
|
35 |
-
|
36 |
-
const server = http.createServer((req, res) => {
|
37 |
-
if (req.url === '/number_statuses.csv') {
|
38 |
-
// Serve the CSV file
|
39 |
-
fs.readFile('number_statuses.csv', (err, data) => {
|
40 |
-
if (err) {
|
41 |
-
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
42 |
-
res.end('Error reading CSV file');
|
43 |
-
} else {
|
44 |
-
res.writeHead(200, { 'Content-Type': 'text/csv' });
|
45 |
-
res.end(data);
|
46 |
-
}
|
47 |
-
});
|
48 |
-
} else if (req.url === '/group_state.json') {
|
49 |
-
// Serve the JSON file
|
50 |
-
fs.readFile('group_state.json', (err, data) => {
|
51 |
-
if (err) {
|
52 |
-
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
53 |
-
res.end('Error reading JSON file');
|
54 |
-
} else {
|
55 |
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
56 |
-
res.end(data);
|
57 |
-
}
|
58 |
-
});
|
59 |
-
} else {
|
60 |
-
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
61 |
-
res.end('File not found');
|
62 |
-
}
|
63 |
-
});
|
64 |
-
|
65 |
-
|
66 |
-
client.on('qr', (qr) => {
|
67 |
-
qrcode.generate(qr, { small: true });
|
68 |
-
});
|
69 |
-
|
70 |
-
client.on('ready', async () => {
|
71 |
-
console.log('Client is ready!');
|
72 |
-
let state = loadState();
|
73 |
-
|
74 |
-
// if (!state) {
|
75 |
-
// state = {
|
76 |
-
// lastProcessedRow: 0,
|
77 |
-
// currentGroupId: null,
|
78 |
-
// currentGroupName: 'عمل 3',
|
79 |
-
// currentGroupCount: 0
|
80 |
-
// };
|
81 |
-
// saveState(state);
|
82 |
-
// }
|
83 |
-
|
84 |
-
// async function processDailyBatch() {
|
85 |
-
// try {
|
86 |
-
// const startFrom = state.lastProcessedRow + 1;
|
87 |
-
// const endTo = startFrom + DAILY_BATCH_SIZE - 1;
|
88 |
-
|
89 |
-
// const numbers = readNumbersFromExcel('numbers.xlsx', startFrom, endTo);
|
90 |
-
// if (numbers.length === 0) {
|
91 |
-
// console.log('All numbers processed.');
|
92 |
-
// return;
|
93 |
-
// }
|
94 |
-
|
95 |
-
// let currentGroup;
|
96 |
-
// if (state.currentGroupId) {
|
97 |
-
// try {
|
98 |
-
// currentGroup = await client.getChatById(state.currentGroupId);
|
99 |
-
// state.currentGroupCount = currentGroup.participants.length;
|
100 |
-
// } catch (error) {
|
101 |
-
// console.error('Error fetching current group:', error);
|
102 |
-
// currentGroup = null;
|
103 |
-
// }
|
104 |
-
// }
|
105 |
-
|
106 |
-
// if (!currentGroup || state.currentGroupCount >= MAX_GROUP_SIZE) {
|
107 |
-
// currentGroup = await createNewGroup(state);
|
108 |
-
// state.currentGroupCount = currentGroup.participants.length;
|
109 |
-
// }
|
110 |
-
|
111 |
-
// const availableSlots = MAX_GROUP_SIZE - state.currentGroupCount;
|
112 |
-
// const numbersToAdd = Math.min(availableSlots, numbers.length);
|
113 |
-
// const batchFrom = startFrom;
|
114 |
-
// const batchTo = batchFrom + numbersToAdd - 1;
|
115 |
-
|
116 |
-
// if (numbersToAdd > 0) {
|
117 |
-
// await checkNumberStatuses(currentGroup.id._serialized, currentGroup.name, 'numbers.xlsx', batchFrom, batchTo);
|
118 |
-
// const updatedGroup = await client.getChatById(currentGroup.id._serialized);
|
119 |
-
// state.currentGroupCount = updatedGroup.participants.length;
|
120 |
-
// state.lastProcessedRow = batchTo;
|
121 |
-
// saveState(state);
|
122 |
-
// }
|
123 |
-
|
124 |
-
// const remainingNumbers = numbers.length - numbersToAdd;
|
125 |
-
// if (remainingNumbers > 0) {
|
126 |
-
// const remainingFrom = batchTo + 1;
|
127 |
-
// const remainingTo = endTo;
|
128 |
-
// const newGroup = await createNewGroup(state);
|
129 |
-
// await checkNumberStatuses(newGroup.id._serialized, newGroup.name, 'numbers.xlsx', remainingFrom, remainingTo);
|
130 |
-
// const updatedNewGroup = await client.getChatById(newGroup.id._serialized);
|
131 |
-
// state.currentGroupCount = updatedNewGroup.participants.length;
|
132 |
-
// state.lastProcessedRow = remainingTo;
|
133 |
-
// saveState(state);
|
134 |
-
// }
|
135 |
-
|
136 |
-
// console.log(`Processed batch from ${startFrom} to ${endTo}`);
|
137 |
-
// } catch (error) {
|
138 |
-
// console.error('Error processing batch:', error);
|
139 |
-
// } finally {
|
140 |
-
// setTimeout(processDailyBatch, 60 * 60 * 1000);
|
141 |
-
// }
|
142 |
-
// }
|
143 |
-
|
144 |
-
// processDailyBatch();
|
145 |
-
});
|
146 |
-
|
147 |
-
client.initialize();
|
148 |
-
|
149 |
-
function loadState() {
|
150 |
-
try {
|
151 |
-
if (fs.existsSync(STATE_FILE)) {
|
152 |
-
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
|
153 |
-
}
|
154 |
-
} catch (error) {
|
155 |
-
console.error('Error loading state:', error);
|
156 |
-
}
|
157 |
-
return null;
|
158 |
-
}
|
159 |
-
|
160 |
-
function saveState(state) {
|
161 |
-
try {
|
162 |
-
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
163 |
-
} catch (error) {
|
164 |
-
console.error('Error saving state:', error);
|
165 |
-
}
|
166 |
-
}
|
167 |
-
async function createNewGroup(state) {
|
168 |
-
try {
|
169 |
-
// C
|
170 |
-
const newGroupName = getNextGroupName(state.currentGroupName);
|
171 |
-
const participants = ['[email protected]', '[email protected]', '[email protected]'];
|
172 |
-
|
173 |
-
|
174 |
-
const creation = await client.createGroup(newGroupName, participants);
|
175 |
-
console.error('Group creation failed:', creation);
|
176 |
-
const newGroup = await client.getChatById(creation.gid._serialized);
|
177 |
-
|
178 |
-
// Promote participants using the proper group instance
|
179 |
-
await newGroup.promoteParticipants(['[email protected]']);
|
180 |
-
|
181 |
-
// Set group settings
|
182 |
-
await newGroup.setMessagesAdminsOnly(true);
|
183 |
-
await newGroup.setInfoAdminsOnly(true);
|
184 |
-
|
185 |
-
// Update state
|
186 |
-
state.currentGroupId = creation.gid._serialized;
|
187 |
-
state.currentGroupName = newGroupName;
|
188 |
-
state.currentGroupCount = newGroup.participants.length;
|
189 |
-
saveState(state);
|
190 |
-
|
191 |
-
return newGroup;
|
192 |
-
} catch (error) {
|
193 |
-
console.error('Group creation failed:', error);
|
194 |
-
throw error;
|
195 |
-
}
|
196 |
-
}
|
197 |
-
|
198 |
-
function getNextGroupName(currentName) {
|
199 |
-
const match = currentName.match(/عمل (\d+)/);
|
200 |
-
if (match) {
|
201 |
-
const num = parseInt(match[1], 10) + 1;
|
202 |
-
return `عمل ${num}`;
|
203 |
-
}
|
204 |
-
return 'عمل 3';
|
205 |
-
}
|
206 |
-
|
207 |
-
function readNumbersFromExcel(filePath, from, to) {
|
208 |
-
const workbook = xlsx.readFile(filePath);
|
209 |
-
const sheetName = workbook.SheetNames[0];
|
210 |
-
const sheet = workbook.Sheets[sheetName];
|
211 |
-
const data = xlsx.utils.sheet_to_json(sheet);
|
212 |
-
if (from < 1 || to > data.length || from > to) {
|
213 |
-
throw new Error('Invalid range specified.');
|
214 |
-
}
|
215 |
-
const numbers = data.slice(from - 1, to).map(row => ({
|
216 |
-
phone: `967${row['phone number']}@c.us`,
|
217 |
-
name: row['name']
|
218 |
-
}));
|
219 |
-
return numbers;
|
220 |
-
}
|
221 |
-
|
222 |
-
async function checkNumberStatuses(groupId, groupName, filePath, from, to) {
|
223 |
-
const numbers = readNumbersFromExcel(filePath, from, to);
|
224 |
-
const groupChat = await client.getChatById(groupId);
|
225 |
-
if (!groupChat.isGroup) {
|
226 |
-
throw new Error('Invalid group ID provided');
|
227 |
-
}
|
228 |
-
|
229 |
-
for (const numberData of numbers) {
|
230 |
-
const resultTemplate = {
|
231 |
-
phone: numberData.phone,
|
232 |
-
name: numberData.name,
|
233 |
-
status: '',
|
234 |
-
error_code: '',
|
235 |
-
message: '',
|
236 |
-
is_invite_sent: false
|
237 |
-
};
|
238 |
-
|
239 |
-
try {
|
240 |
-
const contactId = await client.getNumberId(numberData.phone);
|
241 |
-
if (!contactId) {
|
242 |
-
await csvWriter.writeRecords([{ ...resultTemplate, status: 'UNREGISTERED', error_code: '404', message: 'Not registered on WhatsApp' }]);
|
243 |
-
continue;
|
244 |
-
}
|
245 |
-
|
246 |
-
const addResult = await groupChat.addParticipants([numberData.phone], { autoSendInviteV4: false });
|
247 |
-
const participantResult = addResult[numberData.phone];
|
248 |
-
let resultEntry;
|
249 |
-
|
250 |
-
if (participantResult.code === 200) {
|
251 |
-
resultEntry = { ...resultTemplate, status: 'VALID', error_code: '200', message: 'Successfully added' };
|
252 |
-
} else if (participantResult.code === 403) {
|
253 |
-
resultEntry = { ...resultTemplate, status: 'PRIVATE_INVITE_ONLY', error_code: '403', message: participantResult.message, is_invite_sent: participantResult.isInviteV4Sent };
|
254 |
-
if (participantResult.isInviteV4Sent) {
|
255 |
-
await groupChat.sendInvite(numberData.phone);
|
256 |
-
}
|
257 |
-
} else {
|
258 |
-
resultEntry = { ...resultTemplate, status: 'UNKNOWN_ERROR', error_code: participantResult.code, message: participantResult.message };
|
259 |
-
}
|
260 |
-
|
261 |
-
await csvWriter.writeRecords([resultEntry]);
|
262 |
-
} catch (error) {
|
263 |
-
await csvWriter.writeRecords([{ ...resultTemplate, status: 'UNKNOWN_ERROR', error_code: '500', message: error.message }]);
|
264 |
-
}
|
265 |
-
}
|
266 |
-
}
|
267 |
-
server.listen(7860, () => {
|
268 |
-
console.log('File server running at http://localhost:7860');
|
269 |
-
console.log('Access files at:');
|
270 |
-
console.log('- http://localhost:7860/number_statuses.csv');
|
271 |
-
console.log('- http://localhost:7860/group_state.json');
|
272 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
e.py
ADDED
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import xlsxwriter
|
2 |
+
|
3 |
+
# إنشاء ملف Excel جديد
|
4 |
+
workbook = xlsxwriter.Workbook('AI_telecom.xlsx')
|
5 |
+
|
6 |
+
# تنسيقات الخلايا
|
7 |
+
header_format = workbook.add_format({
|
8 |
+
'bold': True,
|
9 |
+
'bg_color': '#D7E4BC',
|
10 |
+
'border': 1,
|
11 |
+
'align': 'center',
|
12 |
+
'valign': 'vcenter'
|
13 |
+
})
|
14 |
+
cell_format = workbook.add_format({
|
15 |
+
'border': 1,
|
16 |
+
'align': 'center',
|
17 |
+
'valign': 'vcenter',
|
18 |
+
'num_format': '#,##0.00' # رقمين عشريين مع فاصل الآلاف
|
19 |
+
})
|
20 |
+
date_format = workbook.add_format({
|
21 |
+
'border': 1,
|
22 |
+
'align': 'center',
|
23 |
+
'num_format': 'yyyy-mm-dd'
|
24 |
+
})
|
25 |
+
normal_format = workbook.add_format({
|
26 |
+
'border': 1,
|
27 |
+
'align': 'center',
|
28 |
+
'valign': 'vcenter'
|
29 |
+
})
|
30 |
+
|
31 |
+
#################################################
|
32 |
+
# الورقة الأولى: "إحصائيات الذكاء الاصطناعي"
|
33 |
+
#################################################
|
34 |
+
worksheet1 = workbook.add_worksheet('إحصائيات الذكاء الاصطناعي')
|
35 |
+
|
36 |
+
# عناوين الجدول للورقة الأولى
|
37 |
+
headers1 = ['رقم الشركة', 'اسم الشركة', 'عدد العملاء قبل تطبيق الذكاء الاصطناعي',
|
38 |
+
'عدد العملاء بعد تطبيق الذكاء الاصطناعي', 'نسبة الزيادة في العملاء', 'تاريخ التطبيق']
|
39 |
+
for col, header in enumerate(headers1):
|
40 |
+
worksheet1.write(0, col, header, header_format)
|
41 |
+
|
42 |
+
# بيانات حقيقية تقريبية (10 صفوف) من شركات الاتصالات في السعودية:
|
43 |
+
data1 = [
|
44 |
+
[1, 'STC', 20000000, 21000000, None, '2023-01-15'],
|
45 |
+
[2, 'Mobily', 15000000, 15750000, None, '2023-02-10'],
|
46 |
+
[3, 'Zain KSA', 10000000, 10600000, None, '2023-03-05'],
|
47 |
+
[4, 'Virgin Mobile Saudi Arabia', 2000000, 2100000, None, '2023-03-20'],
|
48 |
+
[5, 'Lebara Mobile Saudi Arabia', 1000000, 1080000, None, '2023-04-10'],
|
49 |
+
[6, 'STC - فرع الشرق', 5000000, 5250000, None, '2023-04-15'],
|
50 |
+
[7, 'STC - فرع الغرب', 4500000, 4725000, None, '2023-04-20'],
|
51 |
+
[8, 'Mobily - فرع الرياض', 6000000, 6300000, None, '2023-05-05'],
|
52 |
+
[9, 'Zain KSA - فرع جدة', 3000000, 3150000, None, '2023-05-10'],
|
53 |
+
[10,'Virgin Mobile - فرع الدمام',1500000, 1575000, None, '2023-05-15'],
|
54 |
+
]
|
55 |
+
|
56 |
+
# كتابة البيانات في الجدول مع إضافة صيغة حساب نسبة الزيادة
|
57 |
+
for row_num, row_data in enumerate(data1, start=1):
|
58 |
+
worksheet1.write(row_num, 0, row_data[0], normal_format)
|
59 |
+
worksheet1.write(row_num, 1, row_data[1], normal_format)
|
60 |
+
worksheet1.write_number(row_num, 2, row_data[2], cell_format)
|
61 |
+
worksheet1.write_number(row_num, 3, row_data[3], cell_format)
|
62 |
+
# صيغة حساب نسبة الزيادة: ((عدد العملاء بعد التطبيق - قبل التطبيق) / قبل التطبيق)*100
|
63 |
+
formula = f"=((D{row_num+1}-C{row_num+1})/C{row_num+1})*100"
|
64 |
+
worksheet1.write_formula(row_num, 4, formula, cell_format)
|
65 |
+
worksheet1.write(row_num, 5, row_data[5], date_format)
|
66 |
+
|
67 |
+
# ضبط عرض الأعمدة
|
68 |
+
worksheet1.set_column(0, 0, 10)
|
69 |
+
worksheet1.set_column(1, 1, 30)
|
70 |
+
worksheet1.set_column(2, 3, 25)
|
71 |
+
worksheet1.set_column(4, 4, 20)
|
72 |
+
worksheet1.set_column(5, 5, 15)
|
73 |
+
|
74 |
+
# إضافة مخطط عمودي (Chart) يعرض نسبة الزيادة في العملاء
|
75 |
+
chart1 = workbook.add_chart({'type': 'column'})
|
76 |
+
chart1.add_series({
|
77 |
+
'name': 'نسبة الزيادة في العملاء',
|
78 |
+
'categories': ['إحصائيات الذكاء الاصطناعي', 1, 1, len(data1), 1], # أسماء الشركات (العمود B)
|
79 |
+
'values': ['إحصائيات الذكاء الاصطناعي', 1, 4, len(data1), 4], # النسب المحسوبة (العمود E)
|
80 |
+
})
|
81 |
+
chart1.set_title({'name': 'مخطط نسبة الزيادة في العملاء'})
|
82 |
+
chart1.set_x_axis({'name': 'الشركات'})
|
83 |
+
chart1.set_y_axis({'name': 'النسبة (%)', 'num_format': '0.00'})
|
84 |
+
worksheet1.insert_chart('H2', chart1, {'x_offset': 25, 'y_offset': 10})
|
85 |
+
|
86 |
+
#################################################
|
87 |
+
# الورقة الثانية: "تحليل الأداء"
|
88 |
+
#################################################
|
89 |
+
worksheet2 = workbook.add_worksheet('تحليل الأداء')
|
90 |
+
|
91 |
+
# عناوين الجدول للورقة الثانية
|
92 |
+
headers2 = ['رقم الشركة', 'اسم الشركة', 'الاستثمار في الذكاء الاصطناعي (ملايين ريال)',
|
93 |
+
'زيادة الإيرادات بعد التطبيق (ملايين ريال)', 'العائد على الاستثمار (ROI)', 'ملاحظات']
|
94 |
+
for col, header in enumerate(headers2):
|
95 |
+
worksheet2.write(0, col, header, header_format)
|
96 |
+
|
97 |
+
# بيانات حقيقية تقريبية (10 صفوف) توضح استثمارات وعوائد الذكاء الاصطناعي:
|
98 |
+
data2 = [
|
99 |
+
[1, 'STC', 150, 20, None, 'ROI متوسط'],
|
100 |
+
[2, 'Mobily', 120, 18, None, 'ROI جيد'],
|
101 |
+
[3, 'Zain KSA', 100, 16, None, 'ROI جيد'],
|
102 |
+
[4, 'Virgin Mobile Saudi Arabia', 30, 5, None, 'ROI جيد'],
|
103 |
+
[5, 'Lebara Mobile Saudi Arabia', 25, 4, None, 'ROI جيد'],
|
104 |
+
[6, 'STC - فرع الشرق', 50, 7, None, 'ROI متوسط'],
|
105 |
+
[7, 'STC - فرع الغرب', 45, 6.5, None, 'ROI متوسط'],
|
106 |
+
[8, 'Mobily - فرع الرياض', 60, 9, None, 'ROI جيد'],
|
107 |
+
[9, 'Zain KSA - فرع جدة', 35, 5.5, None, 'ROI جيد'],
|
108 |
+
[10,'Virgin Mobile - فرع الدمام',28, 4.2, None, 'ROI جيد'],
|
109 |
+
]
|
110 |
+
|
111 |
+
# كتابة البيانات في الجدول مع إضافة صيغة حساب ROI باستخدام دالة IF
|
112 |
+
for row_num, row_data in enumerate(data2, start=1):
|
113 |
+
worksheet2.write(row_num, 0, row_data[0], normal_format)
|
114 |
+
worksheet2.write(row_num, 1, row_data[1], normal_format)
|
115 |
+
worksheet2.write_number(row_num, 2, row_data[2], cell_format)
|
116 |
+
worksheet2.write_number(row_num, 3, row_data[3], cell_format)
|
117 |
+
# صيغة حساب ROI: إذا كان الاستثمار 0 فإن ROI = 0، وإلا (زيادة الإيرادات/الاستثمار)*100
|
118 |
+
formula = f"=IF(C{row_num+1}=0, 0, (D{row_num+1}/C{row_num+1})*100)"
|
119 |
+
worksheet2.write_formula(row_num, 4, formula, cell_format)
|
120 |
+
worksheet2.write(row_num, 5, row_data[5], normal_format)
|
121 |
+
|
122 |
+
# ضبط عرض الأعمدة للورقة الثانية
|
123 |
+
worksheet2.set_column(0, 0, 10)
|
124 |
+
worksheet2.set_column(1, 1, 30)
|
125 |
+
worksheet2.set_column(2, 3, 30)
|
126 |
+
worksheet2.set_column(4, 4, 25)
|
127 |
+
worksheet2.set_column(5, 5, 20)
|
128 |
+
|
129 |
+
# إضافة مخطط دائري (Pie Chart) يوضح توزيع الاستثمار في الذكاء الاصطناعي
|
130 |
+
chart2 = workbook.add_chart({'type': 'pie'})
|
131 |
+
chart2.add_series({
|
132 |
+
'name': 'نسبة الاستثمار',
|
133 |
+
'categories': ['تحليل الأداء', 1, 1, len(data2), 1], # أسماء الشركات (العمود B)
|
134 |
+
'values': ['تحليل الأداء', 1, 2, len(data2), 2], # قيم الاستثمار (العمود C)
|
135 |
+
})
|
136 |
+
chart2.set_title({'name': 'مخطط توزيع الاستثمار'})
|
137 |
+
worksheet2.insert_chart('H2', chart2, {'x_offset': 25, 'y_offset': 10})
|
138 |
+
|
139 |
+
#################################################
|
140 |
+
# إغلاق ملف Excel
|
141 |
+
#################################################
|
142 |
+
workbook.close()
|
143 |
+
print("تم إنشاء ملف 'AI_telecom.xlsx' بنجاح!")
|
number_statuses.csv
DELETED
File without changes
|
old/app.js
ADDED
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
i want many account whatsapp running same time to start
|
2 |
+
|
3 |
+
const { Client, LocalAuth } = require('whatsapp-web.js');
|
4 |
+
// const qrcode = require('qrcode-terminal');
|
5 |
+
const xlsx = require('xlsx');
|
6 |
+
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
|
7 |
+
const fs = require('fs');
|
8 |
+
const path = require('path');
|
9 |
+
const http = require('http');
|
10 |
+
|
11 |
+
const server = http.createServer((req, res) => {
|
12 |
+
if (req.url === '/number_statuses.csv') {
|
13 |
+
// Serve the CSV file
|
14 |
+
fs.readFile('number_statuses.csv', (err, data) => {
|
15 |
+
if (err) {
|
16 |
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
17 |
+
res.end('Error reading CSV file');
|
18 |
+
} else {
|
19 |
+
res.writeHead(200, { 'Content-Type': 'text/csv' });
|
20 |
+
res.end(data);
|
21 |
+
}
|
22 |
+
});
|
23 |
+
} else if (req.url === '/group_state.json') {
|
24 |
+
// Serve the JSON file
|
25 |
+
fs.readFile('group_state.json', (err, data) => {
|
26 |
+
if (err) {
|
27 |
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
28 |
+
res.end('Error reading JSON file');
|
29 |
+
} else {
|
30 |
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
31 |
+
res.end(data);
|
32 |
+
}
|
33 |
+
});
|
34 |
+
} else if (req.url === '/qrcode.png') {
|
35 |
+
// Serve the QR code image file
|
36 |
+
fs.readFile('qrcode.png', (err, data) => {
|
37 |
+
if (err) {
|
38 |
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
39 |
+
res.end('Error reading QR code image');
|
40 |
+
} else {
|
41 |
+
res.writeHead(200, { 'Content-Type': 'image/png' });
|
42 |
+
res.end(data);
|
43 |
+
}
|
44 |
+
});
|
45 |
+
} else if (req.url === '/start') {
|
46 |
+
|
47 |
+
|
48 |
+
const STATE_FILE = 'group_state.json';
|
49 |
+
const MAX_GROUP_SIZE = 230;
|
50 |
+
const DAILY_BATCH_SIZE = 1;
|
51 |
+
const qrcode = require('qrcode');
|
52 |
+
|
53 |
+
// CSV Writer configuration
|
54 |
+
const csvWriter = createCsvWriter({
|
55 |
+
path: 'number_statuses.csv',
|
56 |
+
header: [
|
57 |
+
{ id: 'phone', title: 'PHONE' },
|
58 |
+
{ id: 'name', title: 'NAME' },
|
59 |
+
{ id: 'status', title: 'STATUS' },
|
60 |
+
{ id: 'error_code', title: 'ERROR_CODE' },
|
61 |
+
{ id: 'message', title: 'MESSAGE' },
|
62 |
+
{ id: 'is_invite_sent', title: 'INVITE_SENT' }
|
63 |
+
],
|
64 |
+
append: true
|
65 |
+
});
|
66 |
+
const client = new Client({
|
67 |
+
authStrategy: new LocalAuth(),
|
68 |
+
puppeteer: {
|
69 |
+
headless: true,
|
70 |
+
defaultViewport: null,
|
71 |
+
executablePath: '/usr/bin/google-chrome',
|
72 |
+
args: ['--no-sandbox'],
|
73 |
+
}
|
74 |
+
});
|
75 |
+
|
76 |
+
|
77 |
+
|
78 |
+
client.on('qr', (qr) => {
|
79 |
+
// Generate a small QR code for the terminal
|
80 |
+
qrcode.toString(qr, { type: 'terminal', scale: 1 }, (err, url) => {
|
81 |
+
if (err) throw err;
|
82 |
+
console.log(url); // Small QR code printed in the terminal
|
83 |
+
});
|
84 |
+
|
85 |
+
// Alternatively, save the QR code as an image file
|
86 |
+
qrcode.toFile('qrcode.png', qr, { scale: 2 }, (err) => {
|
87 |
+
if (err) throw err;
|
88 |
+
console.log('QR code saved as qrcode.png');
|
89 |
+
});
|
90 |
+
});
|
91 |
+
|
92 |
+
|
93 |
+
client.on('ready', async () => {
|
94 |
+
console.log('Client is ready!');
|
95 |
+
let state = loadState();
|
96 |
+
|
97 |
+
if (!state) {
|
98 |
+
state = {
|
99 |
+
lastProcessedRow: 0,
|
100 |
+
currentGroupId: null,
|
101 |
+
currentGroupName: 'عمل 3',
|
102 |
+
currentGroupCount: 0
|
103 |
+
};
|
104 |
+
saveState(state);
|
105 |
+
}
|
106 |
+
|
107 |
+
async function processDailyBatch() {
|
108 |
+
try {
|
109 |
+
const startFrom = state.lastProcessedRow + 1;
|
110 |
+
const endTo = startFrom + DAILY_BATCH_SIZE - 1;
|
111 |
+
|
112 |
+
const numbers = readNumbersFromExcel('numbers.xlsx', startFrom, endTo);
|
113 |
+
if (numbers.length === 0) {
|
114 |
+
console.log('All numbers processed.');
|
115 |
+
return;
|
116 |
+
}
|
117 |
+
|
118 |
+
let currentGroup;
|
119 |
+
if (state.currentGroupId) {
|
120 |
+
try {
|
121 |
+
currentGroup = await client.getChatById(state.currentGroupId);
|
122 |
+
state.currentGroupCount = currentGroup.participants.length;
|
123 |
+
} catch (error) {
|
124 |
+
console.error('Error fetching current group:', error);
|
125 |
+
currentGroup = null;
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
if (!currentGroup || state.currentGroupCount >= MAX_GROUP_SIZE) {
|
130 |
+
currentGroup = await createNewGroup(state);
|
131 |
+
state.currentGroupCount = currentGroup.participants.length;
|
132 |
+
}
|
133 |
+
|
134 |
+
const availableSlots = MAX_GROUP_SIZE - state.currentGroupCount;
|
135 |
+
const numbersToAdd = Math.min(availableSlots, numbers.length);
|
136 |
+
const batchFrom = startFrom;
|
137 |
+
const batchTo = batchFrom + numbersToAdd - 1;
|
138 |
+
|
139 |
+
if (numbersToAdd > 0) {
|
140 |
+
await checkNumberStatuses(currentGroup.id._serialized, currentGroup.name, 'numbers.xlsx', batchFrom, batchTo);
|
141 |
+
const updatedGroup = await client.getChatById(currentGroup.id._serialized);
|
142 |
+
state.currentGroupCount = updatedGroup.participants.length;
|
143 |
+
state.lastProcessedRow = batchTo;
|
144 |
+
saveState(state);
|
145 |
+
}
|
146 |
+
|
147 |
+
const remainingNumbers = numbers.length - numbersToAdd;
|
148 |
+
if (remainingNumbers > 0) {
|
149 |
+
const remainingFrom = batchTo + 1;
|
150 |
+
const remainingTo = endTo;
|
151 |
+
const newGroup = await createNewGroup(state);
|
152 |
+
await checkNumberStatuses(newGroup.id._serialized, newGroup.name, 'numbers.xlsx', remainingFrom, remainingTo);
|
153 |
+
const updatedNewGroup = await client.getChatById(newGroup.id._serialized);
|
154 |
+
state.currentGroupCount = updatedNewGroup.participants.length;
|
155 |
+
state.lastProcessedRow = remainingTo;
|
156 |
+
saveState(state);
|
157 |
+
}
|
158 |
+
|
159 |
+
console.log(`Processed batch from ${startFrom} to ${endTo}`);
|
160 |
+
} catch (error) {
|
161 |
+
console.error('Error processing batch:', error);
|
162 |
+
} finally {
|
163 |
+
setTimeout(processDailyBatch, 60 * 60 * 1000);
|
164 |
+
}
|
165 |
+
}
|
166 |
+
|
167 |
+
processDailyBatch();
|
168 |
+
});
|
169 |
+
|
170 |
+
client.initialize();
|
171 |
+
|
172 |
+
function loadState() {
|
173 |
+
try {
|
174 |
+
if (fs.existsSync(STATE_FILE)) {
|
175 |
+
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
|
176 |
+
}
|
177 |
+
} catch (error) {
|
178 |
+
console.error('Error loading state:', error);
|
179 |
+
}
|
180 |
+
return null;
|
181 |
+
}
|
182 |
+
|
183 |
+
function saveState(state) {
|
184 |
+
try {
|
185 |
+
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
186 |
+
} catch (error) {
|
187 |
+
console.error('Error saving state:', error);
|
188 |
+
}
|
189 |
+
}
|
190 |
+
async function createNewGroup(state) {
|
191 |
+
try {
|
192 |
+
// C
|
193 |
+
const newGroupName = getNextGroupName(state.currentGroupName);
|
194 |
+
const participants = ['[email protected]', '[email protected]',];
|
195 |
+
|
196 |
+
|
197 |
+
const creation = await client.createGroup(newGroupName, participants);
|
198 |
+
console.error('Group creation failed:', creation);
|
199 |
+
const newGroup = await client.getChatById(creation.gid._serialized);
|
200 |
+
|
201 |
+
// Promote participants using the proper group instance
|
202 |
+
await newGroup.promoteParticipants(['[email protected]']);
|
203 |
+
|
204 |
+
// Set group settings
|
205 |
+
await newGroup.setMessagesAdminsOnly(true);
|
206 |
+
await newGroup.setInfoAdminsOnly(true);
|
207 |
+
|
208 |
+
// Update state
|
209 |
+
state.currentGroupId = creation.gid._serialized;
|
210 |
+
state.currentGroupName = newGroupName;
|
211 |
+
state.currentGroupCount = newGroup.participants.length;
|
212 |
+
saveState(state);
|
213 |
+
|
214 |
+
return newGroup;
|
215 |
+
} catch (error) {
|
216 |
+
console.error('Group creation failed:', error);
|
217 |
+
throw error;
|
218 |
+
}
|
219 |
+
}
|
220 |
+
|
221 |
+
function getNextGroupName(currentName) {
|
222 |
+
const match = currentName.match(/عمل (\d+)/);
|
223 |
+
if (match) {
|
224 |
+
const num = parseInt(match[1], 10) + 1;
|
225 |
+
return `عمل ${num}`;
|
226 |
+
}
|
227 |
+
return 'عمل 3';
|
228 |
+
}
|
229 |
+
|
230 |
+
function readNumbersFromExcel(filePath, from, to) {
|
231 |
+
const workbook = xlsx.readFile(filePath);
|
232 |
+
const sheetName = workbook.SheetNames[0];
|
233 |
+
const sheet = workbook.Sheets[sheetName];
|
234 |
+
const data = xlsx.utils.sheet_to_json(sheet);
|
235 |
+
if (from < 1 || to > data.length || from > to) {
|
236 |
+
throw new Error('Invalid range specified.');
|
237 |
+
}
|
238 |
+
const numbers = data.slice(from - 1, to).map(row => ({
|
239 |
+
phone: `967${row['phone number']}@c.us`,
|
240 |
+
name: row['name']
|
241 |
+
}));
|
242 |
+
return numbers;
|
243 |
+
}
|
244 |
+
|
245 |
+
async function checkNumberStatuses(groupId, groupName, filePath, from, to) {
|
246 |
+
const numbers = readNumbersFromExcel(filePath, from, to);
|
247 |
+
const groupChat = await client.getChatById(groupId);
|
248 |
+
if (!groupChat.isGroup) {
|
249 |
+
throw new Error('Invalid group ID provided');
|
250 |
+
}
|
251 |
+
|
252 |
+
for (const numberData of numbers) {
|
253 |
+
const resultTemplate = {
|
254 |
+
phone: numberData.phone,
|
255 |
+
name: numberData.name,
|
256 |
+
status: '',
|
257 |
+
error_code: '',
|
258 |
+
message: '',
|
259 |
+
is_invite_sent: false
|
260 |
+
};
|
261 |
+
|
262 |
+
try {
|
263 |
+
const contactId = await client.getNumberId(numberData.phone);
|
264 |
+
if (!contactId) {
|
265 |
+
await csvWriter.writeRecords([{ ...resultTemplate, status: 'UNREGISTERED', error_code: '404', message: 'Not registered on WhatsApp' }]);
|
266 |
+
continue;
|
267 |
+
}
|
268 |
+
|
269 |
+
const addResult = await groupChat.addParticipants([numberData.phone], { autoSendInviteV4: false });
|
270 |
+
const participantResult = addResult[numberData.phone];
|
271 |
+
let resultEntry;
|
272 |
+
|
273 |
+
if (participantResult.code === 200) {
|
274 |
+
resultEntry = { ...resultTemplate, status: 'VALID', error_code: '200', message: 'Successfully added' };
|
275 |
+
} else if (participantResult.code === 403) {
|
276 |
+
resultEntry = { ...resultTemplate, status: 'PRIVATE_INVITE_ONLY', error_code: '403', message: participantResult.message, is_invite_sent: participantResult.isInviteV4Sent };
|
277 |
+
if (participantResult.isInviteV4Sent) {
|
278 |
+
await groupChat.sendInvite(numberData.phone);
|
279 |
+
}
|
280 |
+
} else {
|
281 |
+
resultEntry = { ...resultTemplate, status: 'UNKNOWN_ERROR', error_code: participantResult.code, message: participantResult.message };
|
282 |
+
}
|
283 |
+
|
284 |
+
await csvWriter.writeRecords([resultEntry]);
|
285 |
+
} catch (error) {
|
286 |
+
await csvWriter.writeRecords([{ ...resultTemplate, status: 'UNKNOWN_ERROR', error_code: '500', message: error.message }]);
|
287 |
+
}
|
288 |
+
}
|
289 |
+
}
|
290 |
+
|
291 |
+
}
|
292 |
+
else {
|
293 |
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
294 |
+
res.end('File not found');
|
295 |
+
}
|
296 |
+
});
|
297 |
+
|
298 |
+
server.listen(8000, () => {
|
299 |
+
console.log('File server running at http://localhost:8000');
|
300 |
+
console.log('Access files at:');
|
301 |
+
console.log('- http://localhost:8000/number_statuses.csv');
|
302 |
+
console.log('- http://localhost:8000/group_state.json');
|
303 |
+
console.log('- http://localhost:8000/qrcode.png');
|
304 |
+
});
|
305 |
+
|
app.py → old/app.py
RENAMED
File without changes
|
contacts.csv → old/contacts.csv
RENAMED
File without changes
|
d.py → old/d.py
RENAMED
File without changes
|
google_contacts.csv → old/google_contacts.csv
RENAMED
File without changes
|
old/group_state.json
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"lastProcessedRow": 1,
|
3 |
+
"currentGroupId": "[email protected]",
|
4 |
+
"currentGroupName": "عمل 4",
|
5 |
+
"currentGroupCount": 4
|
6 |
+
}
|
history.txt → old/history.txt
RENAMED
File without changes
|
index.js → old/index.js
RENAMED
File without changes
|
old/mapp.js
ADDED
@@ -0,0 +1,422 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const { Client, LocalAuth } = require('whatsapp-web.js');
|
2 |
+
const xlsx = require('xlsx');
|
3 |
+
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
|
4 |
+
const fs = require('fs');
|
5 |
+
const http = require('http');
|
6 |
+
const qrcode = require('qrcode');
|
7 |
+
const path = require('path');
|
8 |
+
|
9 |
+
// Configuration
|
10 |
+
const CONFIG = {
|
11 |
+
PORT: 8081,
|
12 |
+
MAX_GROUP_SIZE: 230,
|
13 |
+
DAILY_BATCH_SIZE: 5,
|
14 |
+
EXCEL_FILE: 'numbers.xlsx',
|
15 |
+
BASE_GROUP_NAME: 'عمل',
|
16 |
+
STATE_FILE: 'shared_state.json',
|
17 |
+
CSV_FILE: 'shared_statuses.csv',
|
18 |
+
SESSION_DIR: 'sessions'
|
19 |
+
};
|
20 |
+
|
21 |
+
const accounts = new Map();
|
22 |
+
|
23 |
+
class WhatsAppAccountManager {
|
24 |
+
constructor(accountId) {
|
25 |
+
this.accountId = accountId;
|
26 |
+
this.status = 'initializing';
|
27 |
+
this.qrFile = path.join(CONFIG.SESSION_DIR, `qrcode_${accountId}.png`);
|
28 |
+
this.sessionPath = path.join(CONFIG.SESSION_DIR, `session_${accountId}`);
|
29 |
+
|
30 |
+
// Create session directory
|
31 |
+
if (!fs.existsSync(this.sessionPath)) {
|
32 |
+
fs.mkdirSync(this.sessionPath, { recursive: true });
|
33 |
+
}
|
34 |
+
|
35 |
+
this.client = new Client({
|
36 |
+
authStrategy: new LocalAuth({ clientId: accountId }),
|
37 |
+
puppeteer: {
|
38 |
+
headless: true,
|
39 |
+
args: [
|
40 |
+
'--no-sandbox',
|
41 |
+
`--user-data-dir=${this.sessionPath}`,
|
42 |
+
'--disable-setuid-sandbox'
|
43 |
+
],
|
44 |
+
}
|
45 |
+
});
|
46 |
+
|
47 |
+
this.csvWriter = createCsvWriter({
|
48 |
+
path: CONFIG.CSV_FILE,
|
49 |
+
header: [
|
50 |
+
{ id: 'phone', title: 'PHONE' },
|
51 |
+
{ id: 'name', title: 'NAME' },
|
52 |
+
{ id: 'status', title: 'STATUS' },
|
53 |
+
{ id: 'error_code', title: 'ERROR_CODE' },
|
54 |
+
{ id: 'message', title: 'MESSAGE' },
|
55 |
+
{ id: 'is_invite_sent', title: 'INVITE_SENT' }
|
56 |
+
],
|
57 |
+
append: true
|
58 |
+
});
|
59 |
+
|
60 |
+
this.setupHandlers();
|
61 |
+
this.initializeClient();
|
62 |
+
}
|
63 |
+
|
64 |
+
setupHandlers() {
|
65 |
+
this.client.on('qr', async (qr) => {
|
66 |
+
this.status = 'awaiting_qr';
|
67 |
+
await qrcode.toFile(this.qrFile, qr, { scale: 2 });
|
68 |
+
console.log(`[${this.accountId}] QR code generated`);
|
69 |
+
});
|
70 |
+
|
71 |
+
this.client.on('ready', () => {
|
72 |
+
this.status = 'ready';
|
73 |
+
console.log(`[${this.accountId}] Client ready`);
|
74 |
+
this.processBatch();
|
75 |
+
});
|
76 |
+
|
77 |
+
this.client.on('disconnected', () => {
|
78 |
+
this.status = 'disconnected';
|
79 |
+
console.log(`[${this.accountId}] Client disconnected`);
|
80 |
+
});
|
81 |
+
}
|
82 |
+
|
83 |
+
initializeClient() {
|
84 |
+
this.client.initialize().catch(err => {
|
85 |
+
console.error(`[${this.accountId}] Initialization error:`, err);
|
86 |
+
this.status = 'error';
|
87 |
+
});
|
88 |
+
}
|
89 |
+
|
90 |
+
loadSharedState() {
|
91 |
+
try {
|
92 |
+
return JSON.parse(fs.readFileSync(CONFIG.STATE_FILE));
|
93 |
+
} catch {
|
94 |
+
return {
|
95 |
+
lastProcessed: 0,
|
96 |
+
currentGroup: null,
|
97 |
+
groupCounter: 1,
|
98 |
+
activeGroups: []
|
99 |
+
};
|
100 |
+
}
|
101 |
+
}
|
102 |
+
|
103 |
+
saveSharedState(state) {
|
104 |
+
fs.writeFileSync(CONFIG.STATE_FILE, JSON.stringify(state, null, 2));
|
105 |
+
}
|
106 |
+
|
107 |
+
readNumbers(lastProcessed) {
|
108 |
+
try {
|
109 |
+
const workbook = xlsx.readFile(CONFIG.EXCEL_FILE);
|
110 |
+
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
111 |
+
const rows = xlsx.utils.sheet_to_json(sheet);
|
112 |
+
|
113 |
+
return rows
|
114 |
+
.slice(lastProcessed, lastProcessed + CONFIG.DAILY_BATCH_SIZE)
|
115 |
+
.map(row => ({
|
116 |
+
phone: `967${row['phone number']}@c.us`,
|
117 |
+
name: row['name']
|
118 |
+
}));
|
119 |
+
} catch (error) {
|
120 |
+
console.error('Error reading numbers:', error);
|
121 |
+
return [];
|
122 |
+
}
|
123 |
+
}
|
124 |
+
|
125 |
+
async processBatch() {
|
126 |
+
if (this.status !== 'ready') return;
|
127 |
+
|
128 |
+
try {
|
129 |
+
const state = this.loadSharedState();
|
130 |
+
const numbers = this.readNumbers(state.lastProcessed);
|
131 |
+
|
132 |
+
if (numbers.length === 0) {
|
133 |
+
console.log(`[${this.accountId}] No numbers to process`);
|
134 |
+
return;
|
135 |
+
}
|
136 |
+
|
137 |
+
let groupId = state.currentGroup;
|
138 |
+
if (!groupId || !await this.verifyGroup(groupId)) {
|
139 |
+
groupId = await this.createGroup(state);
|
140 |
+
}
|
141 |
+
|
142 |
+
await this.processNumbers(groupId, numbers, state);
|
143 |
+
this.saveSharedState(state);
|
144 |
+
|
145 |
+
console.log(`[${this.accountId}] Processed ${numbers.length} numbers`);
|
146 |
+
} catch (error) {
|
147 |
+
console.error(`[${this.accountId}] Batch processing error:`, error);
|
148 |
+
}
|
149 |
+
}
|
150 |
+
|
151 |
+
async verifyGroup(groupId) {
|
152 |
+
try {
|
153 |
+
const group = await this.client.getChatById(groupId);
|
154 |
+
return group.participants.length < CONFIG.MAX_GROUP_SIZE;
|
155 |
+
} catch {
|
156 |
+
return false;
|
157 |
+
}
|
158 |
+
}
|
159 |
+
|
160 |
+
async createGroup(state) {
|
161 |
+
try {
|
162 |
+
const groupName = `${CONFIG.BASE_GROUP_NAME} ${state.groupCounter++}`;
|
163 |
+
const creation = await this.client.createGroup(groupName, [
|
164 |
+
'[email protected]',
|
165 | |
166 |
+
]);
|
167 |
+
|
168 |
+
const group = await this.client.getChatById(creation.gid._serialized);
|
169 |
+
await group.promoteParticipants(['[email protected]']);
|
170 |
+
await group.setMessagesAdminsOnly(true);
|
171 |
+
await group.setInfoAdminsOnly(true);
|
172 |
+
|
173 |
+
state.currentGroup = creation.gid._serialized;
|
174 |
+
state.activeGroups.push(creation.gid._serialized);
|
175 |
+
return creation.gid._serialized;
|
176 |
+
} catch (error) {
|
177 |
+
console.error('Group creation failed:', error);
|
178 |
+
throw error;
|
179 |
+
}
|
180 |
+
}
|
181 |
+
|
182 |
+
async processNumbers(groupId, numbers, state) {
|
183 |
+
try {
|
184 |
+
const group = await this.client.getChatById(groupId);
|
185 |
+
|
186 |
+
for (const { phone, name } of numbers) {
|
187 |
+
const record = {
|
188 |
+
phone,
|
189 |
+
name,
|
190 |
+
status: 'PENDING',
|
191 |
+
error_code: '',
|
192 |
+
message: '',
|
193 |
+
is_invite_sent: false
|
194 |
+
};
|
195 |
+
|
196 |
+
try {
|
197 |
+
const contactId = await this.client.getNumberId(phone);
|
198 |
+
if (!contactId) {
|
199 |
+
record.status = 'INVALID';
|
200 |
+
record.message = 'Number not registered';
|
201 |
+
await this.csvWriter.writeRecords([record]);
|
202 |
+
continue;
|
203 |
+
}
|
204 |
+
|
205 |
+
const result = await group.addParticipants([phone], { autoSendInviteV4: false });
|
206 |
+
const participantResult = result[phone];
|
207 |
+
|
208 |
+
record.status = participantResult.code === 200 ? 'ADDED' : 'FAILED';
|
209 |
+
record.error_code = participantResult.code;
|
210 |
+
record.message = participantResult.message;
|
211 |
+
record.is_invite_sent = participantResult.isInviteV4Sent;
|
212 |
+
|
213 |
+
if (participantResult.code === 403 && !participantResult.isInviteV4Sent) {
|
214 |
+
await group.sendInvite(phone);
|
215 |
+
record.is_invite_sent = true;
|
216 |
+
}
|
217 |
+
|
218 |
+
await this.csvWriter.writeRecords([record]);
|
219 |
+
state.lastProcessed++;
|
220 |
+
} catch (error) {
|
221 |
+
record.status = 'ERROR';
|
222 |
+
record.message = error.message;
|
223 |
+
await this.csvWriter.writeRecords([record]);
|
224 |
+
}
|
225 |
+
}
|
226 |
+
} catch (error) {
|
227 |
+
console.error('Number processing error:', error);
|
228 |
+
}
|
229 |
+
}
|
230 |
+
}
|
231 |
+
|
232 |
+
// HTTP Server
|
233 |
+
const server = http.createServer((req, res) => {
|
234 |
+
if (req.url === '/' && req.method === 'GET') {
|
235 |
+
let accountsHTML = '';
|
236 |
+
accounts.forEach((manager, id) => {
|
237 |
+
accountsHTML += `
|
238 |
+
<div class="account-card">
|
239 |
+
<h3>${id}</h3>
|
240 |
+
<p class="status ${manager.status}">${manager.status.replace('_', ' ')}</p>
|
241 |
+
${manager.status === 'awaiting_qr' ?
|
242 |
+
`<img src="/qrcode/${id}" class="qr-image" alt="QR Code">` :
|
243 |
+
'<p>QR Code unavailable</p>'}
|
244 |
+
<button onclick="startAccount('${id}')">Start Processing</button>
|
245 |
+
</div>
|
246 |
+
`;
|
247 |
+
});
|
248 |
+
|
249 |
+
const html = `
|
250 |
+
<!DOCTYPE html>
|
251 |
+
<html>
|
252 |
+
<head>
|
253 |
+
<title>WhatsApp Bot Manager</title>
|
254 |
+
<style>
|
255 |
+
body { font-family: Arial, sans-serif; padding: 20px; }
|
256 |
+
.account-card {
|
257 |
+
border: 1px solid #ddd;
|
258 |
+
padding: 15px;
|
259 |
+
margin: 10px;
|
260 |
+
border-radius: 5px;
|
261 |
+
max-width: 300px;
|
262 |
+
display: inline-block;
|
263 |
+
vertical-align: top;
|
264 |
+
}
|
265 |
+
.qr-image {
|
266 |
+
max-width: 200px;
|
267 |
+
height: auto;
|
268 |
+
display: block;
|
269 |
+
margin: 10px 0;
|
270 |
+
}
|
271 |
+
button {
|
272 |
+
padding: 8px 16px;
|
273 |
+
background: #007bff;
|
274 |
+
color: white;
|
275 |
+
border: none;
|
276 |
+
border-radius: 4px;
|
277 |
+
cursor: pointer;
|
278 |
+
}
|
279 |
+
button:hover { background: #0056b3; }
|
280 |
+
form { margin: 20px 0; }
|
281 |
+
input {
|
282 |
+
padding: 8px;
|
283 |
+
margin-right: 10px;
|
284 |
+
border: 1px solid #ddd;
|
285 |
+
border-radius: 4px;
|
286 |
+
}
|
287 |
+
.status {
|
288 |
+
padding: 4px 8px;
|
289 |
+
border-radius: 4px;
|
290 |
+
font-size: 0.9em;
|
291 |
+
display: inline-block;
|
292 |
+
}
|
293 |
+
.status.ready { background: #d4edda; color: #155724; }
|
294 |
+
.status.awaiting_qr { background: #fff3cd; color: #856404; }
|
295 |
+
</style>
|
296 |
+
</head>
|
297 |
+
<body>
|
298 |
+
<h1>WhatsApp Bot Manager</h1>
|
299 |
+
|
300 |
+
<form onsubmit="event.preventDefault(); addAccount()">
|
301 |
+
<input type="text" id="newAccountId" placeholder="Enter account ID">
|
302 |
+
<button type="submit">Add New Account</button>
|
303 |
+
</form>
|
304 |
+
|
305 |
+
<div id="accounts">${accountsHTML}</div>
|
306 |
+
|
307 |
+
<script>
|
308 |
+
function addAccount() {
|
309 |
+
const accountId = document.getElementById('newAccountId').value;
|
310 |
+
if (!accountId) return;
|
311 |
+
|
312 |
+
fetch('/add-account', {
|
313 |
+
method: 'POST',
|
314 |
+
headers: { 'Content-Type': 'application/json' },
|
315 |
+
body: JSON.stringify({ accountId })
|
316 |
+
})
|
317 |
+
.then(response => response.json())
|
318 |
+
.then(data => {
|
319 |
+
if (data.success) {
|
320 |
+
location.reload();
|
321 |
+
} else {
|
322 |
+
alert(data.error || 'Error adding account');
|
323 |
+
}
|
324 |
+
});
|
325 |
+
}
|
326 |
+
|
327 |
+
function startAccount(accountId) {
|
328 |
+
fetch('/start/' + accountId, { method: 'POST' })
|
329 |
+
.then(response => response.json())
|
330 |
+
.then(data => {
|
331 |
+
if (!data.success) {
|
332 |
+
alert('Error: ' + (data.error || 'Unknown error'));
|
333 |
+
}
|
334 |
+
});
|
335 |
+
}
|
336 |
+
|
337 |
+
// Auto-refresh QR codes every 3 seconds
|
338 |
+
// setInterval(() => {
|
339 |
+
// document.querySelectorAll('.qr-image').forEach(img => {
|
340 |
+
// img.src = img.src.split('?')[0] + '?t=' + Date.now();
|
341 |
+
// });
|
342 |
+
// }, 3000);
|
343 |
+
</script>
|
344 |
+
</body>
|
345 |
+
</html>
|
346 |
+
`;
|
347 |
+
|
348 |
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
349 |
+
res.end(html);
|
350 |
+
}
|
351 |
+
else if (req.method === 'POST' && req.url === '/add-account') {
|
352 |
+
let body = '';
|
353 |
+
req.on('data', chunk => body += chunk);
|
354 |
+
req.on('end', () => {
|
355 |
+
try {
|
356 |
+
const { accountId } = JSON.parse(body);
|
357 |
+
if (!accountId) {
|
358 |
+
res.writeHead(400);
|
359 |
+
return res.end(JSON.stringify({ error: 'Account ID required' }));
|
360 |
+
}
|
361 |
+
|
362 |
+
if (accounts.has(accountId)) {
|
363 |
+
res.writeHead(409);
|
364 |
+
return res.end(JSON.stringify({ error: 'Account already exists' }));
|
365 |
+
}
|
366 |
+
|
367 |
+
accounts.set(accountId, new WhatsAppAccountManager(accountId));
|
368 |
+
res.writeHead(201);
|
369 |
+
res.end(JSON.stringify({ success: true }));
|
370 |
+
} catch (error) {
|
371 |
+
res.writeHead(500);
|
372 |
+
res.end(JSON.stringify({ error: 'Invalid request' }));
|
373 |
+
}
|
374 |
+
});
|
375 |
+
}
|
376 |
+
else if (req.method === 'POST' && req.url.startsWith('/start/')) {
|
377 |
+
const accountId = req.url.split('/')[2];
|
378 |
+
const manager = accounts.get(accountId);
|
379 |
+
|
380 |
+
if (!manager) {
|
381 |
+
res.writeHead(404);
|
382 |
+
return res.end(JSON.stringify({ error: 'Account not found' }));
|
383 |
+
}
|
384 |
+
|
385 |
+
manager.processBatch();
|
386 |
+
res.writeHead(200);
|
387 |
+
res.end(JSON.stringify({ success: true }));
|
388 |
+
}
|
389 |
+
else if (req.url.startsWith('/qrcode/')) {
|
390 |
+
const accountId = req.url.split('/')[2];
|
391 |
+
const manager = accounts.get(accountId);
|
392 |
+
|
393 |
+
if (!manager) {
|
394 |
+
res.writeHead(404);
|
395 |
+
return res.end('Account not found');
|
396 |
+
}
|
397 |
+
|
398 |
+
fs.readFile(manager.qrFile, (err, data) => {
|
399 |
+
if (err) {
|
400 |
+
res.writeHead(404);
|
401 |
+
res.end('QR code not available');
|
402 |
+
} else {
|
403 |
+
res.writeHead(200, { 'Content-Type': 'image/png' });
|
404 |
+
res.end(data);
|
405 |
+
}
|
406 |
+
});
|
407 |
+
}
|
408 |
+
else {
|
409 |
+
res.writeHead(404);
|
410 |
+
res.end('Not found');
|
411 |
+
}
|
412 |
+
});
|
413 |
+
|
414 |
+
// Initialize session directory
|
415 |
+
if (!fs.existsSync(CONFIG.SESSION_DIR)) {
|
416 |
+
fs.mkdirSync(CONFIG.SESSION_DIR, { recursive: true });
|
417 |
+
}
|
418 |
+
|
419 |
+
// Start server
|
420 |
+
server.listen(CONFIG.PORT, () => {
|
421 |
+
console.log(`Server running on http://localhost:${CONFIG.PORT}`);
|
422 |
+
});
|
old/number_statuses.csv
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
[email protected],رقم خاص ع / م 59000,VALID,200,Successfully added,false
|
number_statuses1.csv → old/number_statuses1.csv
RENAMED
File without changes
|
old/numbers.xlsx
ADDED
Binary file (23.4 kB). View file
|
|
old/qrcode.png
ADDED
![]() |
package.json
CHANGED
@@ -13,6 +13,7 @@
|
|
13 |
"console.table": "^0.10.0",
|
14 |
"csv-writer": "^1.6.0",
|
15 |
"puppeteer": "^24.1.1",
|
|
|
16 |
"qrcode-terminal": "^0.12.0",
|
17 |
"whatsapp-web.js": "1.26.1-alpha.3",
|
18 |
"xlsx": "^0.18.5"
|
|
|
13 |
"console.table": "^0.10.0",
|
14 |
"csv-writer": "^1.6.0",
|
15 |
"puppeteer": "^24.1.1",
|
16 |
+
"qrcode": "^1.5.4",
|
17 |
"qrcode-terminal": "^0.12.0",
|
18 |
"whatsapp-web.js": "1.26.1-alpha.3",
|
19 |
"xlsx": "^0.18.5"
|
pnpm-lock.yaml
CHANGED
@@ -17,6 +17,9 @@ importers:
|
|
17 |
puppeteer:
|
18 |
specifier: ^24.1.1
|
19 |
version: 24.1.1
|
|
|
|
|
|
|
20 |
qrcode-terminal:
|
21 |
specifier: ^0.12.0
|
22 |
version: 0.12.0
|
@@ -171,6 +174,10 @@ packages:
|
|
171 |
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
172 |
engines: {node: '>=6'}
|
173 |
|
|
|
|
|
|
|
|
|
174 | |
175 |
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
|
176 |
engines: {node: '>=0.8'}
|
@@ -186,6 +193,9 @@ packages:
|
|
186 |
peerDependencies:
|
187 |
devtools-protocol: '*'
|
188 |
|
|
|
|
|
|
|
189 | |
190 |
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
191 |
engines: {node: '>=12'}
|
@@ -265,6 +275,10 @@ packages:
|
|
265 |
supports-color:
|
266 |
optional: true
|
267 |
|
|
|
|
|
|
|
|
|
268 | |
269 |
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
|
270 |
|
@@ -278,6 +292,9 @@ packages:
|
|
278 | |
279 |
resolution: {integrity: sha512-1CJABgqLxbYxVI+uJY/UDUHJtJ0KZTSjNYJYKqd9FRoXT33WDakDHNxRapMEgzeJ/C3rcs01+avshMnPmKQbvA==}
|
280 |
|
|
|
|
|
|
|
281 | |
282 |
resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
|
283 |
|
@@ -330,6 +347,10 @@ packages:
|
|
330 | |
331 |
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
332 |
|
|
|
|
|
|
|
|
|
333 | |
334 |
resolution: {integrity: sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==}
|
335 |
engines: {node: '>=0.8.0'}
|
@@ -441,6 +462,10 @@ packages:
|
|
441 | |
442 |
resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==}
|
443 |
|
|
|
|
|
|
|
|
|
444 | |
445 |
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
446 |
|
@@ -523,6 +548,18 @@ packages:
|
|
523 | |
524 |
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
525 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
526 | |
527 |
resolution: {integrity: sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==}
|
528 |
engines: {node: '>= 14'}
|
@@ -539,6 +576,10 @@ packages:
|
|
539 |
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
540 |
engines: {node: '>=8'}
|
541 |
|
|
|
|
|
|
|
|
|
542 | |
543 |
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
544 |
engines: {node: '>=0.10.0'}
|
@@ -549,6 +590,10 @@ packages:
|
|
549 | |
550 |
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
551 |
|
|
|
|
|
|
|
|
|
552 | |
553 |
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
554 |
|
@@ -588,6 +633,11 @@ packages:
|
|
588 |
resolution: {integrity: sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==}
|
589 |
hasBin: true
|
590 |
|
|
|
|
|
|
|
|
|
|
|
591 | |
592 |
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
593 |
|
@@ -602,6 +652,9 @@ packages:
|
|
602 |
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
603 |
engines: {node: '>=0.10.0'}
|
604 |
|
|
|
|
|
|
|
605 | |
606 |
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
607 |
engines: {node: '>=4'}
|
@@ -627,6 +680,9 @@ packages:
|
|
627 |
engines: {node: '>=10'}
|
628 |
hasBin: true
|
629 |
|
|
|
|
|
|
|
630 | |
631 |
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
632 |
|
@@ -730,6 +786,9 @@ packages:
|
|
730 | |
731 |
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
732 |
|
|
|
|
|
|
|
733 | |
734 |
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
|
735 |
hasBin: true
|
@@ -742,6 +801,10 @@ packages:
|
|
742 |
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
743 |
engines: {node: '>=0.8'}
|
744 |
|
|
|
|
|
|
|
|
|
745 | |
746 |
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
747 |
engines: {node: '>=10'}
|
@@ -778,14 +841,25 @@ packages:
|
|
778 |
engines: {node: '>=0.8'}
|
779 |
hasBin: true
|
780 |
|
|
|
|
|
|
|
781 | |
782 |
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
783 |
engines: {node: '>=10'}
|
784 |
|
|
|
|
|
|
|
|
|
785 | |
786 |
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
787 |
engines: {node: '>=12'}
|
788 |
|
|
|
|
|
|
|
|
|
789 | |
790 |
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
791 |
engines: {node: '>=12'}
|
@@ -979,6 +1053,8 @@ snapshots:
|
|
979 |
|
980 | |
981 |
|
|
|
|
|
982 | |
983 |
dependencies:
|
984 |
adler-32: 1.3.1
|
@@ -997,6 +1073,12 @@ snapshots:
|
|
997 |
mitt: 3.0.1
|
998 |
zod: 3.24.1
|
999 |
|
|
|
|
|
|
|
|
|
|
|
|
|
1000 | |
1001 |
dependencies:
|
1002 |
string-width: 4.2.3
|
@@ -1064,6 +1146,8 @@ snapshots:
|
|
1064 |
dependencies:
|
1065 |
ms: 2.1.3
|
1066 |
|
|
|
|
|
1067 | |
1068 |
dependencies:
|
1069 |
clone: 1.0.4
|
@@ -1079,6 +1163,8 @@ snapshots:
|
|
1079 |
|
1080 | |
1081 |
|
|
|
|
|
1082 | |
1083 |
dependencies:
|
1084 |
readable-stream: 2.3.8
|
@@ -1132,6 +1218,11 @@ snapshots:
|
|
1132 |
dependencies:
|
1133 |
pend: 1.2.0
|
1134 |
|
|
|
|
|
|
|
|
|
|
|
1135 | |
1136 |
dependencies:
|
1137 |
async: 3.2.6
|
@@ -1260,6 +1351,10 @@ snapshots:
|
|
1260 | |
1261 |
optional: true
|
1262 |
|
|
|
|
|
|
|
|
|
1263 | |
1264 |
optional: true
|
1265 |
|
@@ -1323,6 +1418,16 @@ snapshots:
|
|
1323 |
dependencies:
|
1324 |
wrappy: 1.0.2
|
1325 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1326 | |
1327 |
dependencies:
|
1328 |
'@tootallnate/quickjs-emscripten': 0.23.0
|
@@ -1352,12 +1457,16 @@ snapshots:
|
|
1352 |
json-parse-even-better-errors: 2.3.1
|
1353 |
lines-and-columns: 1.2.4
|
1354 |
|
|
|
|
|
1355 | |
1356 |
|
1357 | |
1358 |
|
1359 | |
1360 |
|
|
|
|
|
1361 | |
1362 |
optional: true
|
1363 |
|
@@ -1444,6 +1553,12 @@ snapshots:
|
|
1444 |
|
1445 | |
1446 |
|
|
|
|
|
|
|
|
|
|
|
|
|
1447 | |
1448 |
dependencies:
|
1449 |
core-util-is: 1.0.3
|
@@ -1468,6 +1583,8 @@ snapshots:
|
|
1468 |
|
1469 | |
1470 |
|
|
|
|
|
1471 | |
1472 |
|
1473 | |
@@ -1486,6 +1603,8 @@ snapshots:
|
|
1486 |
|
1487 | |
1488 |
|
|
|
|
|
1489 | |
1490 |
optional: true
|
1491 |
|
@@ -1642,6 +1761,8 @@ snapshots:
|
|
1642 |
tr46: 0.0.3
|
1643 |
webidl-conversions: 3.0.1
|
1644 |
|
|
|
|
|
1645 | |
1646 |
dependencies:
|
1647 |
isexe: 2.0.0
|
@@ -1650,6 +1771,12 @@ snapshots:
|
|
1650 |
|
1651 | |
1652 |
|
|
|
|
|
|
|
|
|
|
|
|
|
1653 | |
1654 |
dependencies:
|
1655 |
ansi-styles: 4.3.0
|
@@ -1672,10 +1799,31 @@ snapshots:
|
|
1672 |
wmf: 1.0.2
|
1673 |
word: 0.3.0
|
1674 |
|
|
|
|
|
1675 | |
1676 |
|
|
|
|
|
|
|
|
|
|
|
1677 | |
1678 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1679 | |
1680 |
dependencies:
|
1681 |
cliui: 8.0.1
|
|
|
17 |
puppeteer:
|
18 |
specifier: ^24.1.1
|
19 |
version: 24.1.1
|
20 |
+
qrcode:
|
21 |
+
specifier: ^1.5.4
|
22 |
+
version: 1.5.4
|
23 |
qrcode-terminal:
|
24 |
specifier: ^0.12.0
|
25 |
version: 0.12.0
|
|
|
174 |
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
175 |
engines: {node: '>=6'}
|
176 |
|
177 | |
178 |
+
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
|
179 |
+
engines: {node: '>=6'}
|
180 |
+
|
181 | |
182 |
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
|
183 |
engines: {node: '>=0.8'}
|
|
|
193 |
peerDependencies:
|
194 |
devtools-protocol: '*'
|
195 |
|
196 | |
197 |
+
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
198 |
+
|
199 | |
200 |
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
201 |
engines: {node: '>=12'}
|
|
|
275 |
supports-color:
|
276 |
optional: true
|
277 |
|
278 | |
279 |
+
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
280 |
+
engines: {node: '>=0.10.0'}
|
281 |
+
|
282 | |
283 |
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
|
284 |
|
|
|
292 | |
293 |
resolution: {integrity: sha512-1CJABgqLxbYxVI+uJY/UDUHJtJ0KZTSjNYJYKqd9FRoXT33WDakDHNxRapMEgzeJ/C3rcs01+avshMnPmKQbvA==}
|
294 |
|
295 | |
296 |
+
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
297 |
+
|
298 | |
299 |
resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
|
300 |
|
|
|
347 | |
348 |
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
349 |
|
350 | |
351 |
+
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
352 |
+
engines: {node: '>=8'}
|
353 |
+
|
354 | |
355 |
resolution: {integrity: sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==}
|
356 |
engines: {node: '>=0.8.0'}
|
|
|
462 | |
463 |
resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==}
|
464 |
|
465 | |
466 |
+
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
467 |
+
engines: {node: '>=8'}
|
468 |
+
|
469 | |
470 |
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
471 |
|
|
|
548 | |
549 |
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
550 |
|
551 | |
552 |
+
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
553 |
+
engines: {node: '>=6'}
|
554 |
+
|
555 | |
556 |
+
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
557 |
+
engines: {node: '>=8'}
|
558 |
+
|
559 | |
560 |
+
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
561 |
+
engines: {node: '>=6'}
|
562 |
+
|
563 | |
564 |
resolution: {integrity: sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==}
|
565 |
engines: {node: '>= 14'}
|
|
|
576 |
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
577 |
engines: {node: '>=8'}
|
578 |
|
579 | |
580 |
+
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
581 |
+
engines: {node: '>=8'}
|
582 |
+
|
583 | |
584 |
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
585 |
engines: {node: '>=0.10.0'}
|
|
|
590 | |
591 |
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
592 |
|
593 | |
594 |
+
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
595 |
+
engines: {node: '>=10.13.0'}
|
596 |
+
|
597 | |
598 |
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
599 |
|
|
|
633 |
resolution: {integrity: sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==}
|
634 |
hasBin: true
|
635 |
|
636 | |
637 |
+
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
638 |
+
engines: {node: '>=10.13.0'}
|
639 |
+
hasBin: true
|
640 |
+
|
641 | |
642 |
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
643 |
|
|
|
652 |
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
653 |
engines: {node: '>=0.10.0'}
|
654 |
|
655 | |
656 |
+
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
657 |
+
|
658 | |
659 |
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
660 |
engines: {node: '>=4'}
|
|
|
680 |
engines: {node: '>=10'}
|
681 |
hasBin: true
|
682 |
|
683 | |
684 |
+
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
685 |
+
|
686 | |
687 |
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
688 |
|
|
|
786 | |
787 |
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
788 |
|
789 | |
790 |
+
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
791 |
+
|
792 | |
793 |
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
|
794 |
hasBin: true
|
|
|
801 |
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
802 |
engines: {node: '>=0.8'}
|
803 |
|
804 | |
805 |
+
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
806 |
+
engines: {node: '>=8'}
|
807 |
+
|
808 | |
809 |
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
810 |
engines: {node: '>=10'}
|
|
|
841 |
engines: {node: '>=0.8'}
|
842 |
hasBin: true
|
843 |
|
844 | |
845 |
+
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
846 |
+
|
847 | |
848 |
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
849 |
engines: {node: '>=10'}
|
850 |
|
851 | |
852 |
+
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
853 |
+
engines: {node: '>=6'}
|
854 |
+
|
855 | |
856 |
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
857 |
engines: {node: '>=12'}
|
858 |
|
859 | |
860 |
+
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
861 |
+
engines: {node: '>=8'}
|
862 |
+
|
863 | |
864 |
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
865 |
engines: {node: '>=12'}
|
|
|
1053 |
|
1054 | |
1055 |
|
1056 |
+
[email protected]: {}
|
1057 |
+
|
1058 | |
1059 |
dependencies:
|
1060 |
adler-32: 1.3.1
|
|
|
1073 |
mitt: 3.0.1
|
1074 |
zod: 3.24.1
|
1075 |
|
1076 | |
1077 |
+
dependencies:
|
1078 |
+
string-width: 4.2.3
|
1079 |
+
strip-ansi: 6.0.1
|
1080 |
+
wrap-ansi: 6.2.0
|
1081 |
+
|
1082 | |
1083 |
dependencies:
|
1084 |
string-width: 4.2.3
|
|
|
1146 |
dependencies:
|
1147 |
ms: 2.1.3
|
1148 |
|
1149 |
+
[email protected]: {}
|
1150 |
+
|
1151 | |
1152 |
dependencies:
|
1153 |
clone: 1.0.4
|
|
|
1163 |
|
1164 | |
1165 |
|
1166 |
+
[email protected]: {}
|
1167 |
+
|
1168 | |
1169 |
dependencies:
|
1170 |
readable-stream: 2.3.8
|
|
|
1218 |
dependencies:
|
1219 |
pend: 1.2.0
|
1220 |
|
1221 | |
1222 |
+
dependencies:
|
1223 |
+
locate-path: 5.0.0
|
1224 |
+
path-exists: 4.0.0
|
1225 |
+
|
1226 | |
1227 |
dependencies:
|
1228 |
async: 3.2.6
|
|
|
1351 | |
1352 |
optional: true
|
1353 |
|
1354 | |
1355 |
+
dependencies:
|
1356 |
+
p-locate: 4.1.0
|
1357 |
+
|
1358 | |
1359 |
optional: true
|
1360 |
|
|
|
1418 |
dependencies:
|
1419 |
wrappy: 1.0.2
|
1420 |
|
1421 | |
1422 |
+
dependencies:
|
1423 |
+
p-try: 2.2.0
|
1424 |
+
|
1425 | |
1426 |
+
dependencies:
|
1427 |
+
p-limit: 2.3.0
|
1428 |
+
|
1429 |
+
[email protected]: {}
|
1430 |
+
|
1431 | |
1432 |
dependencies:
|
1433 |
'@tootallnate/quickjs-emscripten': 0.23.0
|
|
|
1457 |
json-parse-even-better-errors: 2.3.1
|
1458 |
lines-and-columns: 1.2.4
|
1459 |
|
1460 |
+
[email protected]: {}
|
1461 |
+
|
1462 | |
1463 |
|
1464 | |
1465 |
|
1466 | |
1467 |
|
1468 |
+
[email protected]: {}
|
1469 |
+
|
1470 | |
1471 |
optional: true
|
1472 |
|
|
|
1553 |
|
1554 | |
1555 |
|
1556 | |
1557 |
+
dependencies:
|
1558 |
+
dijkstrajs: 1.0.3
|
1559 |
+
pngjs: 5.0.0
|
1560 |
+
yargs: 15.4.1
|
1561 |
+
|
1562 | |
1563 |
dependencies:
|
1564 |
core-util-is: 1.0.3
|
|
|
1583 |
|
1584 | |
1585 |
|
1586 |
+
[email protected]: {}
|
1587 |
+
|
1588 | |
1589 |
|
1590 | |
|
|
1603 |
|
1604 | |
1605 |
|
1606 |
+
[email protected]: {}
|
1607 |
+
|
1608 | |
1609 |
optional: true
|
1610 |
|
|
|
1761 |
tr46: 0.0.3
|
1762 |
webidl-conversions: 3.0.1
|
1763 |
|
1764 |
+
[email protected]: {}
|
1765 |
+
|
1766 | |
1767 |
dependencies:
|
1768 |
isexe: 2.0.0
|
|
|
1771 |
|
1772 | |
1773 |
|
1774 | |
1775 |
+
dependencies:
|
1776 |
+
ansi-styles: 4.3.0
|
1777 |
+
string-width: 4.2.3
|
1778 |
+
strip-ansi: 6.0.1
|
1779 |
+
|
1780 | |
1781 |
dependencies:
|
1782 |
ansi-styles: 4.3.0
|
|
|
1799 |
wmf: 1.0.2
|
1800 |
word: 0.3.0
|
1801 |
|
1802 |
+
[email protected]: {}
|
1803 |
+
|
1804 | |
1805 |
|
1806 | |
1807 |
+
dependencies:
|
1808 |
+
camelcase: 5.3.1
|
1809 |
+
decamelize: 1.2.0
|
1810 |
+
|
1811 | |
1812 |
|
1813 | |
1814 |
+
dependencies:
|
1815 |
+
cliui: 6.0.0
|
1816 |
+
decamelize: 1.2.0
|
1817 |
+
find-up: 4.1.0
|
1818 |
+
get-caller-file: 2.0.5
|
1819 |
+
require-directory: 2.1.1
|
1820 |
+
require-main-filename: 2.0.0
|
1821 |
+
set-blocking: 2.0.0
|
1822 |
+
string-width: 4.2.3
|
1823 |
+
which-module: 2.0.1
|
1824 |
+
y18n: 4.0.3
|
1825 |
+
yargs-parser: 18.1.3
|
1826 |
+
|
1827 | |
1828 |
dependencies:
|
1829 |
cliui: 8.0.1
|
r.sh
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
-
sudo docker build -t whatsapp-bot .
|
2 |
|
3 |
-
sudo docker run -it --name whatsapp-bot-container whatsapp-bot
|
|
|
1 |
+
# sudo docker build -t whatsapp-bot .
|
2 |
|
3 |
+
# sudo docker run -it --name whatsapp-bot-container whatsapp-bot
|
run.sh
CHANGED
@@ -1 +1 @@
|
|
1 |
-
node index.js
|
|
|
1 |
+
node src/index.js
|
src/AccountManager.js
ADDED
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const { Client, LocalAuth } = require('whatsapp-web.js');
|
2 |
+
const xlsx = require('xlsx');
|
3 |
+
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
|
4 |
+
const fs = require('fs');
|
5 |
+
const path = require('path');
|
6 |
+
const qrcode = require('qrcode');
|
7 |
+
const CONFIG = require('./config');
|
8 |
+
|
9 |
+
class WhatsAppAccountManager {
|
10 |
+
constructor(accountId) {
|
11 |
+
this.accountId = accountId;
|
12 |
+
this.status = 'initializing';
|
13 |
+
this.qrFile = path.join(CONFIG.SESSION_DIR, `qrcode_${accountId}.png`);
|
14 |
+
this.sessionPath = path.join(CONFIG.SESSION_DIR, `session_${accountId}`);
|
15 |
+
this.processDetails = [];
|
16 |
+
if (!fs.existsSync(this.sessionPath)) {
|
17 |
+
fs.mkdirSync(this.sessionPath, { recursive: true });
|
18 |
+
}
|
19 |
+
|
20 |
+
this.client = new Client({
|
21 |
+
authStrategy: new LocalAuth({ clientId: accountId }),
|
22 |
+
|
23 |
+
puppeteer: {
|
24 |
+
headless: true,
|
25 |
+
executablePath: '/usr/bin/google-chrome',
|
26 |
+
args: [
|
27 |
+
'--no-sandbox',
|
28 |
+
`--user-data-dir=${this.sessionPath}`,
|
29 |
+
'--disable-setuid-sandbox'
|
30 |
+
],
|
31 |
+
}
|
32 |
+
});
|
33 |
+
|
34 |
+
this.csvWriter = createCsvWriter({
|
35 |
+
path: CONFIG.CSV_FILE,
|
36 |
+
header: [
|
37 |
+
{ id: 'phone', title: 'PHONE' },
|
38 |
+
{ id: 'name', title: 'NAME' },
|
39 |
+
{ id: 'status', title: 'STATUS' },
|
40 |
+
{ id: 'error_code', title: 'ERROR_CODE' },
|
41 |
+
{ id: 'message', title: 'MESSAGE' },
|
42 |
+
{ id: 'is_invite_sent', title: 'INVITE_SENT' }
|
43 |
+
],
|
44 |
+
append: true
|
45 |
+
});
|
46 |
+
|
47 |
+
this.setupHandlers();
|
48 |
+
this.initializeClient();
|
49 |
+
}
|
50 |
+
|
51 |
+
|
52 |
+
setupHandlers() {
|
53 |
+
this.client.on('qr', async (qr) => {
|
54 |
+
this.status = 'awaiting_qr';
|
55 |
+
await qrcode.toFile(this.qrFile, qr, { scale: 2 });
|
56 |
+
console.log(`[${this.accountId}] QR code generated`);
|
57 |
+
});
|
58 |
+
|
59 |
+
this.client.on('ready', () => {
|
60 |
+
this.status = 'ready';
|
61 |
+
console.log(`[${this.accountId}] Client ready`);
|
62 |
+
this.processBatch();
|
63 |
+
});
|
64 |
+
|
65 |
+
this.client.on('disconnected', () => {
|
66 |
+
this.status = 'disconnected';
|
67 |
+
console.log(`[${this.accountId}] Client disconnected`);
|
68 |
+
});
|
69 |
+
}
|
70 |
+
|
71 |
+
initializeClient() {
|
72 |
+
this.client.initialize().catch(err => {
|
73 |
+
console.error(`[${this.accountId}] Initialization error:`, err);
|
74 |
+
this.status = 'error';
|
75 |
+
});
|
76 |
+
}
|
77 |
+
|
78 |
+
loadSharedState() {
|
79 |
+
try {
|
80 |
+
return JSON.parse(fs.readFileSync(CONFIG.STATE_FILE));
|
81 |
+
} catch {
|
82 |
+
return {
|
83 |
+
lastProcessed: 0,
|
84 |
+
currentGroup: null,
|
85 |
+
groupCounter: 1,
|
86 |
+
activeGroups: []
|
87 |
+
};
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
+
saveSharedState(state) {
|
92 |
+
fs.writeFileSync(CONFIG.STATE_FILE, JSON.stringify(state, null, 2));
|
93 |
+
}
|
94 |
+
|
95 |
+
readNumbers(lastProcessed) {
|
96 |
+
try {
|
97 |
+
const workbook = xlsx.readFile(CONFIG.EXCEL_FILE);
|
98 |
+
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
99 |
+
const rows = xlsx.utils.sheet_to_json(sheet);
|
100 |
+
|
101 |
+
return rows
|
102 |
+
.slice(lastProcessed, lastProcessed + CONFIG.DAILY_BATCH_SIZE)
|
103 |
+
.map(row => ({
|
104 |
+
phone: `967${row['phone number']}@c.us`,
|
105 |
+
name: row['name']
|
106 |
+
}));
|
107 |
+
} catch (error) {
|
108 |
+
console.error('Error reading numbers:', error);
|
109 |
+
return [];
|
110 |
+
}
|
111 |
+
}
|
112 |
+
|
113 |
+
async processBatch() {
|
114 |
+
if (this.status !== 'running') return;
|
115 |
+
|
116 |
+
try {
|
117 |
+
const state = this.loadSharedState();
|
118 |
+
const numbers = this.readNumbers(state.lastProcessed);
|
119 |
+
|
120 |
+
if (numbers.length === 0) {
|
121 |
+
console.log(`[${this.accountId}] No numbers to process`);
|
122 |
+
return;
|
123 |
+
}
|
124 |
+
|
125 |
+
let groupId = state.currentGroup;
|
126 |
+
if (!groupId || !await this.verifyGroup(groupId)) {
|
127 |
+
groupId = await this.createGroup(state);
|
128 |
+
}
|
129 |
+
|
130 |
+
await this.processNumbers(groupId, numbers, state);
|
131 |
+
|
132 |
+
this.saveSharedState(state);
|
133 |
+
|
134 |
+
console.log(`[${this.accountId}] Processed ${numbers.length} numbers`);
|
135 |
+
} catch (error) {
|
136 |
+
console.error(`[${this.accountId}] Batch processing error:`, error);
|
137 |
+
}
|
138 |
+
}
|
139 |
+
|
140 |
+
async verifyGroup(groupId) {
|
141 |
+
try {
|
142 |
+
const group = await this.client.getChatById(groupId);
|
143 |
+
return group.participants.length < CONFIG.MAX_GROUP_SIZE;
|
144 |
+
} catch {
|
145 |
+
return false;
|
146 |
+
}
|
147 |
+
}
|
148 |
+
|
149 |
+
async createGroup(state) {
|
150 |
+
try {
|
151 |
+
const numberAdded = [
|
152 |
+
'[email protected]', //1
|
153 |
+
'[email protected]', //2
|
154 |
+
'[email protected]',//3
|
155 |
+
'[email protected]',//4
|
156 |
+
'[email protected]',//5
|
157 |
+
|
158 |
+
'[email protected]',//mohammed old
|
159 |
+
|
160 |
+
'[email protected]',//6
|
161 |
+
'[email protected]',//7
|
162 |
+
|
163 |
+
|
164 |
+
|
165 |
+
'[email protected]', //unclue
|
166 |
+
'[email protected]' //mohammed alhas
|
167 |
+
]
|
168 |
+
|
169 |
+
|
170 |
+
const groupName = `${CONFIG.BASE_GROUP_NAME} ${state.groupCounter++}`;
|
171 |
+
const creation = await this.client.createGroup(groupName, numberAdded);
|
172 |
+
|
173 |
+
const group = await this.client.getChatById(creation.gid._serialized);
|
174 |
+
await group.promoteParticipants([
|
175 |
+
'[email protected]',
|
176 |
+
'[email protected]',
|
177 |
+
'[email protected]',
|
178 |
+
'[email protected]',
|
179 |
+
'[email protected]',
|
180 |
+
'[email protected]',
|
181 |
+
'[email protected]',]);
|
182 |
+
await group.setMessagesAdminsOnly(true);
|
183 |
+
await group.setInfoAdminsOnly(true);
|
184 |
+
|
185 |
+
state.currentGroup = creation.gid._serialized;
|
186 |
+
state.activeGroups.push(creation.gid._serialized);
|
187 |
+
return creation.gid._serialized;
|
188 |
+
} catch (error) {
|
189 |
+
console.error('Group creation failed:', error);
|
190 |
+
throw error;
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
async processNumbers(groupId, numbers, state) {
|
195 |
+
try {
|
196 |
+
const group = await this.client.getChatById(groupId);
|
197 |
+
|
198 |
+
for (const { phone, name } of numbers) {
|
199 |
+
const record = {
|
200 |
+
phone,
|
201 |
+
name,
|
202 |
+
status: 'PENDING',
|
203 |
+
error_code: '',
|
204 |
+
message: '',
|
205 |
+
is_invite_sent: false
|
206 |
+
};
|
207 |
+
|
208 |
+
try {
|
209 |
+
const contactId = await this.client.getNumberId(phone);
|
210 |
+
if (!contactId) {
|
211 |
+
record.status = 'INVALID';
|
212 |
+
record.message = 'Number not registered';
|
213 |
+
await this.csvWriter.writeRecords([record]);
|
214 |
+
continue;
|
215 |
+
}
|
216 |
+
|
217 |
+
const result = await group.addParticipants([phone], { autoSendInviteV4: false });
|
218 |
+
const participantResult = result[phone];
|
219 |
+
|
220 |
+
record.status = participantResult.code === 200 ? 'ADDED' : 'FAILED';
|
221 |
+
record.error_code = participantResult.code;
|
222 |
+
record.message = participantResult.message;
|
223 |
+
record.is_invite_sent = participantResult.isInviteV4Sent;
|
224 |
+
|
225 |
+
if (participantResult.code === 403 && !participantResult.isInviteV4Sent) {
|
226 |
+
await group.sendInvite(phone);
|
227 |
+
record.is_invite_sent = true;
|
228 |
+
}
|
229 |
+
this.processDetails.push(record);
|
230 |
+
await this.csvWriter.writeRecords([record]);
|
231 |
+
state.lastProcessed++;
|
232 |
+
} catch (error) {
|
233 |
+
record.status = 'ERROR';
|
234 |
+
record.message = error.message;
|
235 |
+
this.processDetails.push(record);
|
236 |
+
await this.csvWriter.writeRecords([record]);
|
237 |
+
}
|
238 |
+
}
|
239 |
+
} catch (error) {
|
240 |
+
console.error('Number processing error:', error);
|
241 |
+
}
|
242 |
+
}
|
243 |
+
|
244 |
+
|
245 |
+
async addNumberToGroup(phone, groupName) {
|
246 |
+
try {
|
247 |
+
if (!phone.endsWith('@c.us')) {
|
248 |
+
phone = phone.replace(/\D/g, ''); // Remove non-numeric characters
|
249 |
+
if (!phone.startsWith('967')) {
|
250 |
+
phone = `967${phone}`; // Add Yemen country code if missing
|
251 |
+
}
|
252 |
+
phone = `${phone}@c.us`; // Append @c.us suffix
|
253 |
+
}
|
254 |
+
|
255 |
+
console.log(`Formatted phone number: ${phone}`);
|
256 |
+
|
257 |
+
// Get all chats and find the group
|
258 |
+
const chats = await this.client.getChats();
|
259 |
+
const group = chats.find(chat => chat.isGroup && chat.name === groupName);
|
260 |
+
|
261 |
+
if (!group) {
|
262 |
+
console.error(`Group '${groupName}' not found.`);
|
263 |
+
return { success: false, message: "Group not found" };
|
264 |
+
}
|
265 |
+
|
266 |
+
const record = {
|
267 |
+
phone,
|
268 |
+
status: 'PENDING',
|
269 |
+
error_code: '',
|
270 |
+
message: '',
|
271 |
+
is_invite_sent: false
|
272 |
+
};
|
273 |
+
|
274 |
+
// Check if number is valid
|
275 |
+
const contactId = await this.client.getNumberId(phone);
|
276 |
+
if (!contactId) {
|
277 |
+
record.status = 'INVALID';
|
278 |
+
record.message = 'Number not registered';
|
279 |
+
await this.csvWriter.writeRecords([record]);
|
280 |
+
return { success: false, message: "Number not registered" };
|
281 |
+
}
|
282 |
+
|
283 |
+
// Add participant
|
284 |
+
const result = await group.addParticipants([phone], { autoSendInviteV4: false });
|
285 |
+
const participantResult = result[phone];
|
286 |
+
|
287 |
+
record.status = participantResult.code === 200 ? 'ADDED' : 'FAILED';
|
288 |
+
record.error_code = participantResult.code;
|
289 |
+
record.message = participantResult.message;
|
290 |
+
record.is_invite_sent = participantResult.isInviteV4Sent;
|
291 |
+
|
292 |
+
// Handle invitation if needed
|
293 |
+
if (participantResult.code === 403 && !participantResult.isInviteV4Sent) {
|
294 |
+
await group.sendInvite(phone);
|
295 |
+
record.is_invite_sent = true;
|
296 |
+
}
|
297 |
+
|
298 |
+
await this.csvWriter.writeRecords([record]);
|
299 |
+
|
300 |
+
return { success: participantResult.code === 200, message: participantResult.message };
|
301 |
+
|
302 |
+
} catch (error) {
|
303 |
+
console.error('Error adding number to group:', error);
|
304 |
+
return { success: false, message: error.message };
|
305 |
+
}
|
306 |
+
}
|
307 |
+
|
308 |
+
}
|
309 |
+
module.exports = WhatsAppAccountManager;
|
src/config.js
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const path = require('path');
|
2 |
+
|
3 |
+
module.exports = {
|
4 |
+
PORT: 8081,
|
5 |
+
MAX_GROUP_SIZE: 230,
|
6 |
+
DAILY_BATCH_SIZE: 1,
|
7 |
+
EXCEL_FILE: 'numbers.xlsx',
|
8 |
+
BASE_GROUP_NAME: 'عمل',
|
9 |
+
STATE_FILE: 'shared_state.json',
|
10 |
+
CSV_FILE: 'shared_statuses.csv',
|
11 |
+
SESSION_DIR: path.join(__dirname, 'sessions'),
|
12 |
+
STATE_REFRESH_INTERVAL: 5000
|
13 |
+
};
|
src/index.js
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const http = require('http');
|
2 |
+
const CONFIG = require('./config');
|
3 |
+
const routes = require('./routes');
|
4 |
+
const { accounts } = require('./shared');
|
5 |
+
const fs = require('fs');
|
6 |
+
const server = http.createServer((req, res) => {
|
7 |
+
routes(req, res);
|
8 |
+
});
|
9 |
+
|
10 |
+
// Initialize session directory
|
11 |
+
if (!fs.existsSync(CONFIG.SESSION_DIR)) {
|
12 |
+
fs.mkdirSync(CONFIG.SESSION_DIR, { recursive: true });
|
13 |
+
}
|
14 |
+
|
15 |
+
server.listen(CONFIG.PORT, () => {
|
16 |
+
console.log(`Server running on http://localhost:${CONFIG.PORT}`);
|
17 |
+
});
|
src/numbers.xlsx
ADDED
Binary file (23.4 kB). View file
|
|
src/routes.js
ADDED
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const fs = require('fs');
|
2 |
+
const path = require('path');
|
3 |
+
const CONFIG = require('./config');
|
4 |
+
const WhatsAppAccountManager = require('./AccountManager');
|
5 |
+
const { accounts } = require('./shared');
|
6 |
+
|
7 |
+
const htmlTemplate = fs.readFileSync(path.join(__dirname, 'views', 'index.html'), 'utf8');
|
8 |
+
|
9 |
+
module.exports = function (req, res) {
|
10 |
+
if (req.url === '/app' && req.method === 'GET') {
|
11 |
+
handleRoot(req, res);
|
12 |
+
} else if (req.method === 'POST' && req.url === '/add-account') {
|
13 |
+
handleAddAccount(req, res);
|
14 |
+
} else if (req.method === 'POST' && req.url.startsWith('/start/')) {
|
15 |
+
handleStartAccount(req, res);
|
16 |
+
} else if (req.url.startsWith('/qrcode/')) {
|
17 |
+
handleQrCode(req, res);
|
18 |
+
}
|
19 |
+
else if (req.url === '/shared_state.json') {
|
20 |
+
handleSharedState(req, res);
|
21 |
+
} else if (req.url === '/shared_statuses.csv') {
|
22 |
+
handleSharedStatuses(req, res);
|
23 |
+
}else if (req.url === '/dashboard-data') {
|
24 |
+
handleDashboardData(req, res);
|
25 |
+
}else if (req.url === '/accounts-status') {
|
26 |
+
handleAccountsStatus(req, res);
|
27 |
+
} else if (req.method === 'POST' && req.url === '/add-number-to-group') {
|
28 |
+
// Handle adding number to group
|
29 |
+
let body = '';
|
30 |
+
req.on('data', chunk => body += chunk);
|
31 |
+
req.on('end', async () => {
|
32 |
+
try {
|
33 |
+
const { accountId, phone, groupName } = JSON.parse(body);
|
34 |
+
const manager = accounts.get(accountId);
|
35 |
+
|
36 |
+
if (!manager) {
|
37 |
+
res.writeHead(404);
|
38 |
+
return res.end(JSON.stringify({ success: false, message: 'Account not found' }));
|
39 |
+
}
|
40 |
+
|
41 |
+
const result = await manager.addNumberToGroup(phone, groupName);
|
42 |
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
43 |
+
res.end(JSON.stringify(result));
|
44 |
+
} catch (error) {
|
45 |
+
res.writeHead(500);
|
46 |
+
res.end(JSON.stringify({ success: false, message: error.message }));
|
47 |
+
}
|
48 |
+
});
|
49 |
+
}
|
50 |
+
else {
|
51 |
+
res.writeHead(404);
|
52 |
+
res.end('Not found');
|
53 |
+
}
|
54 |
+
};
|
55 |
+
|
56 |
+
function handleAccountsStatus(req, res) {
|
57 |
+
const accountsData = Array.from(accounts).map(([id, manager]) => ({
|
58 |
+
id,
|
59 |
+
status: manager.status
|
60 |
+
}));
|
61 |
+
|
62 |
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
63 |
+
res.end(JSON.stringify({ accounts: accountsData }));
|
64 |
+
}
|
65 |
+
|
66 |
+
|
67 |
+
function checkSessionFolders() {
|
68 |
+
const sessionDir = CONFIG.SESSION_DIR;
|
69 |
+
|
70 |
+
// Check if the session directory exists
|
71 |
+
if (!fs.existsSync(sessionDir)) {
|
72 |
+
console.log('Session directory does not exist.');
|
73 |
+
return;
|
74 |
+
}
|
75 |
+
|
76 |
+
// Read all files/folders in the session directory
|
77 |
+
const sessionFolders = fs.readdirSync(sessionDir);
|
78 |
+
|
79 |
+
sessionFolders.forEach(folder => {
|
80 |
+
// Only process folders that start with "session_"
|
81 |
+
if (folder.startsWith('session_') && !folder.endsWith('.png')) {
|
82 |
+
// Extract account ID from folder name (e.g., "session_123456")
|
83 |
+
const accountId = folder.replace('session_', '');
|
84 |
+
|
85 |
+
// Check if the account ID is not in the accounts map
|
86 |
+
if (!accounts.has(accountId)) {
|
87 |
+
console.log(`Found session folder for account ${accountId}, but no manager exists. Creating new manager...`);
|
88 |
+
|
89 |
+
// Create a new WhatsAppAccountManager for this account
|
90 |
+
accounts.set(accountId, new WhatsAppAccountManager(accountId));
|
91 |
+
}
|
92 |
+
}
|
93 |
+
});
|
94 |
+
}
|
95 |
+
|
96 |
+
function handleAddAccount(req, res) {
|
97 |
+
let body = '';
|
98 |
+
req.on('data', chunk => body += chunk);
|
99 |
+
req.on('end', () => {
|
100 |
+
try {
|
101 |
+
const { accountId } = JSON.parse(body);
|
102 |
+
if (!accountId) {
|
103 |
+
res.writeHead(400);
|
104 |
+
return res.end(JSON.stringify({ error: 'Account ID required' }));
|
105 |
+
}
|
106 |
+
|
107 |
+
if (accounts.has(accountId)) {
|
108 |
+
res.writeHead(409);
|
109 |
+
return res.end(JSON.stringify({ error: 'Account already exists' }));
|
110 |
+
}
|
111 |
+
|
112 |
+
accounts.set(accountId, new WhatsAppAccountManager(accountId));
|
113 |
+
res.writeHead(201);
|
114 |
+
res.end(JSON.stringify({ success: true }));
|
115 |
+
} catch (error) {
|
116 |
+
res.writeHead(500);
|
117 |
+
res.end(JSON.stringify({ error: 'Invalid request' }));
|
118 |
+
}
|
119 |
+
});
|
120 |
+
}
|
121 |
+
|
122 |
+
function handleStartAccount(req, res) {
|
123 |
+
const accountId = req.url.split('/')[2];
|
124 |
+
const manager = accounts.get(accountId);
|
125 |
+
|
126 |
+
if (!manager) {
|
127 |
+
res.writeHead(404);
|
128 |
+
return res.end(JSON.stringify({ error: 'Account not found' }));
|
129 |
+
}
|
130 |
+
|
131 |
+
manager.status = 'running';
|
132 |
+
manager.processBatch();
|
133 |
+
res.writeHead(200);
|
134 |
+
res.end(JSON.stringify({ success: true }));
|
135 |
+
}
|
136 |
+
|
137 |
+
function handleQrCode(req, res) {
|
138 |
+
const accountId = req.url.split('/')[2];
|
139 |
+
const manager = accounts.get(accountId);
|
140 |
+
|
141 |
+
if (!manager) {
|
142 |
+
res.writeHead(404);
|
143 |
+
return res.end('Account not found');
|
144 |
+
}
|
145 |
+
|
146 |
+
fs.readFile(manager.qrFile, (err, data) => {
|
147 |
+
if (err) {
|
148 |
+
res.writeHead(404);
|
149 |
+
res.end('QR code not available');
|
150 |
+
} else {
|
151 |
+
res.writeHead(200, { 'Content-Type': 'image/png' });
|
152 |
+
res.end(data);
|
153 |
+
}
|
154 |
+
});
|
155 |
+
}
|
156 |
+
|
157 |
+
// Add these new handler functions
|
158 |
+
function handleSharedState(req, res) {
|
159 |
+
fs.readFile(CONFIG.STATE_FILE, (err, data) => {
|
160 |
+
if (err) {
|
161 |
+
res.writeHead(404);
|
162 |
+
res.end('State file not found');
|
163 |
+
} else {
|
164 |
+
res.writeHead(200, {
|
165 |
+
'Content-Type': 'application/json',
|
166 |
+
'Content-Disposition': 'attachment; filename="shared_state.json"'
|
167 |
+
});
|
168 |
+
res.end(data);
|
169 |
+
}
|
170 |
+
});
|
171 |
+
}
|
172 |
+
|
173 |
+
function handleSharedStatuses(req, res) {
|
174 |
+
fs.readFile(CONFIG.CSV_FILE, (err, data) => {
|
175 |
+
if (err) {
|
176 |
+
res.writeHead(404);
|
177 |
+
res.end('Status file not found');
|
178 |
+
} else {
|
179 |
+
res.writeHead(200, {
|
180 |
+
'Content-Type': 'text/csv',
|
181 |
+
'Content-Disposition': 'attachment; filename="shared_statuses.csv"'
|
182 |
+
});
|
183 |
+
res.end(data);
|
184 |
+
}
|
185 |
+
});
|
186 |
+
}
|
187 |
+
|
188 |
+
|
189 |
+
|
190 |
+
function handleRoot(req, res) {
|
191 |
+
checkSessionFolders();
|
192 |
+
let state = {};
|
193 |
+
try {
|
194 |
+
state = JSON.parse(fs.readFileSync(CONFIG.STATE_FILE));
|
195 |
+
} catch (error) {
|
196 |
+
state = {
|
197 |
+
currentGroup: null,
|
198 |
+
groupCounter: 1,
|
199 |
+
activeGroups: [],
|
200 |
+
lastProcessed: 0
|
201 |
+
};
|
202 |
+
}
|
203 |
+
|
204 |
+
|
205 |
+
|
206 |
+
let accountsHTML = '';
|
207 |
+
accounts.forEach((manager, id) => {
|
208 |
+
accountsHTML += generateAccountCard(id, manager);
|
209 |
+
});
|
210 |
+
|
211 |
+
let html = htmlTemplate
|
212 |
+
.replace('{{ACCOUNTS}}', accountsHTML);
|
213 |
+
|
214 |
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
215 |
+
res.end(html);
|
216 |
+
}
|
217 |
+
|
218 |
+
function generateAccountCard(accountId, manager) {
|
219 |
+
let content = '';
|
220 |
+
const baseContent =
|
221 |
+
`<h3 class='text-xl font-semibold text-gray-900 mb-2'>${accountId}</h3>
|
222 |
+
<p class='text-sm font-medium py-1 px-3 rounded-full text-white ${manager.status === 'ready' ? 'bg-green-500' : manager.status === 'running' ? 'bg-blue-500' : 'bg-yellow-500'}'>
|
223 |
+
${manager.status.replace('_', ' ')}
|
224 |
+
</p>`;
|
225 |
+
|
226 |
+
if (manager.status === 'initializing') {
|
227 |
+
content =
|
228 |
+
`${baseContent}
|
229 |
+
<div class='flex items-center gap-2 text-gray-600 mt-2'>
|
230 |
+
<i class='fas fa-spinner fa-spin'></i>
|
231 |
+
<span>Initializing...</span>
|
232 |
+
</div>`;
|
233 |
+
} else if (manager.status === 'awaiting_qr') {
|
234 |
+
content =
|
235 |
+
`${baseContent}
|
236 |
+
<div class='flex flex-col items-center mt-4'>
|
237 |
+
<img src='/qrcode/${accountId}' class='w-64 h-64 rounded-lg shadow-md border' alt='QR Code'>
|
238 |
+
<p class='mt-2 text-gray-600'>Scan this QR code with your phone</p>
|
239 |
+
</div>`;
|
240 |
+
} else if (manager.status === 'ready') {
|
241 |
+
content =
|
242 |
+
`${baseContent}
|
243 |
+
<button onclick="startAccount('${accountId}')" class='w-full mt-4 px-6 py-2 bg-blue-600 text-white rounded-lg shadow-md hover:bg-blue-700 flex items-center justify-center'>
|
244 |
+
<i class='fas fa-play mr-2'></i> Start Processing
|
245 |
+
</button>`;
|
246 |
+
} else if (manager.status === 'running') {
|
247 |
+
content =
|
248 |
+
`${baseContent}
|
249 |
+
<div class='flex items-center gap-2 text-gray-600 mt-2'>
|
250 |
+
<i class='fas fa-spinner fa-spin'></i>
|
251 |
+
<span>Processing...</span>
|
252 |
+
</div>`;
|
253 |
+
} else {
|
254 |
+
content = baseContent;
|
255 |
+
}
|
256 |
+
|
257 |
+
const processDetails = manager.status === 'running' ? `
|
258 |
+
<div class="mt-4 w-full">
|
259 |
+
<h4 class="text-lg font-semibold mb-2">Process Details</h4>
|
260 |
+
<button onclick="toggleDetails('${accountId}')" class="text-blue-600 hover:text-blue-800 text-sm">
|
261 |
+
${manager.processDetailsOpen ? 'Hide Details' : 'Show Details'}
|
262 |
+
</button>
|
263 |
+
<div id="details-${accountId}" class="details-section ${manager.processDetailsOpen ? '' : 'hidden'}">
|
264 |
+
<table class="w-full border-collapse border border-gray-300">
|
265 |
+
<thead>
|
266 |
+
<tr class="bg-gray-200">
|
267 |
+
<th class="border border-gray-300 px-4 py-2">Phone</th>
|
268 |
+
<th class="border border-gray-300 px-4 py-2">Name</th>
|
269 |
+
<th class="border border-gray-300 px-4 py-2">Status</th>
|
270 |
+
<th class="border border-gray-300 px-4 py-2">Error Code</th>
|
271 |
+
<th class="border border-gray-300 px-4 py-2">Message</th>
|
272 |
+
<th class="border border-gray-300 px-4 py-2">Invite Sent</th>
|
273 |
+
</tr>
|
274 |
+
</thead>
|
275 |
+
<tbody>
|
276 |
+
${manager.processDetails.map(detail => `
|
277 |
+
<tr>
|
278 |
+
<td class="border border-gray-300 px-4 py-2">${detail.phone}</td>
|
279 |
+
<td class="border border-gray-300 px-4 py-2">${detail.name}</td>
|
280 |
+
<td class="border border-gray-300 px-4 py-2">${detail.status}</td>
|
281 |
+
<td class="border border-gray-300 px-4 py-2">${detail.error_code || 'N/A'}</td>
|
282 |
+
<td class="border border-gray-300 px-4 py-2">${detail.message || 'N/A'}</td>
|
283 |
+
<td class="border border-gray-300 px-4 py-2">${detail.is_invite_sent || 'N/A'}</td>
|
284 |
+
</tr>
|
285 |
+
`).join('')}
|
286 |
+
</tbody>
|
287 |
+
</table>
|
288 |
+
</div>
|
289 |
+
</div>
|
290 |
+
` : '';
|
291 |
+
|
292 |
+
return `
|
293 |
+
<div class='bg-white p-6 rounded-lg shadow-lg border flex flex-col items-center' data-status="${manager.status}" data-account-id="${accountId}">
|
294 |
+
${content}
|
295 |
+
${processDetails}
|
296 |
+
</div>`;
|
297 |
+
}
|
298 |
+
|
299 |
+
|
300 |
+
function handleDashboardData(req, res) {
|
301 |
+
try {
|
302 |
+
const state = JSON.parse(fs.readFileSync(CONFIG.STATE_FILE));
|
303 |
+
const data = {
|
304 |
+
currentGroupName: state.currentGroupName ?
|
305 |
+
`${CONFIG.BASE_GROUP_NAME} ${state.groupCounter - 1}` : 'No active group',
|
306 |
+
currentGroupMembers: state.currentGroup ?
|
307 |
+
(state.activeGroups.find(g => g.id === state.currentGroup)?.members || 0) : 0,
|
308 |
+
maxGroupSize: CONFIG.MAX_GROUP_SIZE,
|
309 |
+
currentGroupId: state.currentGroup ?
|
310 |
+
state.currentGroup.substring(0, 8) + '...' : 'N/A',
|
311 |
+
totalProcessed: state.lastProcessed,
|
312 |
+
activeGroupsCount: state.activeGroups.length,
|
313 |
+
nextGroupName: `${CONFIG.BASE_GROUP_NAME} ${state.groupCounter}`
|
314 |
+
};
|
315 |
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
316 |
+
res.end(JSON.stringify(data));
|
317 |
+
} catch (error) {
|
318 |
+
res.writeHead(500);
|
319 |
+
res.end(JSON.stringify({ error: 'Could not load dashboard data' }));
|
320 |
+
}
|
321 |
+
}
|
src/shared.js
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
accounts: new Map()
|
3 |
+
};
|
src/shared_state.json
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"lastProcessed": 52,
|
3 |
+
"currentGroup": "[email protected]",
|
4 |
+
"currentGroupName": "عمل 4",
|
5 |
+
"groupCounter": 4,
|
6 |
+
"activeGroups": [
|
7 | |
8 |
+
]
|
9 |
+
}
|
src/shared_statuses.csv
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
735201518,,INVALID,,Number not registered,false
|
2 |
+
[email protected],,INVALID,,Number not registered,false
|
3 |
+
[email protected],,INVALID,,Number not registered,false
|
4 |
+
[email protected],رقم خاص ع / م 59049,ADDED,200,The participant was added successfully,false
|
5 |
+
[email protected],رقم خاص ع / م 59050,ADDED,200,The participant was added successfully,false
|
6 |
+
[email protected],,ADDED,200,The participant was added successfully,false
|
7 |
+
[email protected],رقم خاص ع / م 59051,ADDED,200,The participant was added successfully,false
|
src/views/index.html
ADDED
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>WhatsApp Bot Manager</title>
|
7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
9 |
+
</head>
|
10 |
+
<body class="bg-gray-100 p-6">
|
11 |
+
<!-- Add Number Modal -->
|
12 |
+
<div id="addNumberModal" class="fixed inset-0 bg-black bg-opacity-50 hidden">
|
13 |
+
<div class="bg-white rounded-lg p-6 max-w-md mx-auto mt-20">
|
14 |
+
<h3 class="text-xl font-bold mb-4">Add Number to Group</h3>
|
15 |
+
<form id="addNumberForm">
|
16 |
+
<div class="mb-4">
|
17 |
+
<label class="block mb-2">Phone Number</label>
|
18 |
+
<input type="text" id="phoneNumber" class="w-full px-3 py-2 border rounded" required>
|
19 |
+
</div>
|
20 |
+
<div class="mb-4">
|
21 |
+
<label class="block mb-2">Group Name</label>
|
22 |
+
<input type="text" id="groupName" class="w-full px-3 py-2 border rounded" required>
|
23 |
+
</div>
|
24 |
+
<div class="mb-4">
|
25 |
+
<label class="block mb-2">Account ID</label>
|
26 |
+
<input type="text" id="accountId" class="w-full px-3 py-2 border rounded" required>
|
27 |
+
<p class="text-sm text-gray-500 mt-1">Enter the account ID of a ready account</p>
|
28 |
+
</div>
|
29 |
+
<div class="flex justify-end">
|
30 |
+
<button type="button" onclick="closeModal()" class="mr-2 px-4 py-2 bg-gray-500 text-white rounded">Cancel</button>
|
31 |
+
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded">Add Number</button>
|
32 |
+
</div>
|
33 |
+
</form>
|
34 |
+
</div>
|
35 |
+
</div>
|
36 |
+
|
37 |
+
<!-- Main Content -->
|
38 |
+
<div class="bg-white shadow-lg border rounded-lg p-4 mb-6 flex justify-between items-center">
|
39 |
+
<h1 class="text-3xl font-bold bg-gradient-to-r from-blue-500 to-purple-600 text-transparent bg-clip-text">WhatsApp Bot Manager</h1>
|
40 |
+
<button onclick="location.reload()" class="px-6 py-2 bg-gray-600 text-white rounded-lg shadow-md hover:bg-gray-700 flex items-center">
|
41 |
+
<i class="fas fa-sync-alt mr-2"></i> Refresh
|
42 |
+
</button>
|
43 |
+
</div>
|
44 |
+
|
45 |
+
<div class="flex justify-center space-x-4 mb-6">
|
46 |
+
<a href="/shared_statuses.csv" download class="px-6 py-2 bg-green-600 text-white rounded-lg shadow-md hover:bg-green-700">Download Status Report</a>
|
47 |
+
<a href="/shared_state.json" download class="px-6 py-2 bg-blue-600 text-white rounded-lg shadow-md hover:bg-blue-700">View Shared State</a>
|
48 |
+
<button onclick="openAddNumberModal()" class="px-6 py-2 bg-purple-600 text-white rounded-lg shadow-md hover:bg-purple-700">
|
49 |
+
<i class="fas fa-plus mr-2"></i>Add Number to Group
|
50 |
+
</button>
|
51 |
+
</div>
|
52 |
+
|
53 |
+
<form onsubmit="event.preventDefault(); addAccount()" class="flex justify-center mb-6">
|
54 |
+
<input type="text" id="newAccountId" placeholder="Enter account ID" class="px-4 py-2 border rounded-lg shadow-md w-1/3">
|
55 |
+
<button type="submit" class="ml-4 px-6 py-2 bg-indigo-600 text-white rounded-lg shadow-md hover:bg-indigo-700">Add New Account</button>
|
56 |
+
</form>
|
57 |
+
|
58 |
+
<div id="accounts" class="grid grid-cols-1 md:grid-cols-2 gap-6">{{ACCOUNTS}}</div>
|
59 |
+
|
60 |
+
<script>
|
61 |
+
function openAddNumberModal() {
|
62 |
+
document.getElementById('addNumberModal').classList.remove('hidden');
|
63 |
+
}
|
64 |
+
|
65 |
+
function closeModal() {
|
66 |
+
document.getElementById('addNumberModal').classList.add('hidden');
|
67 |
+
}
|
68 |
+
|
69 |
+
async function addNumberToGroup() {
|
70 |
+
const phone = document.getElementById('phoneNumber').value;
|
71 |
+
const groupName = document.getElementById('groupName').value;
|
72 |
+
const accountId = document.getElementById('accountId').value;
|
73 |
+
|
74 |
+
// Validate account status
|
75 |
+
|
76 |
+
|
77 |
+
try {
|
78 |
+
const response = await fetch('/add-number-to-group', {
|
79 |
+
method: 'POST',
|
80 |
+
headers: { 'Content-Type': 'application/json' },
|
81 |
+
body: JSON.stringify({ accountId, phone, groupName })
|
82 |
+
});
|
83 |
+
|
84 |
+
const result = await response.json();
|
85 |
+
if (result.success) {
|
86 |
+
alert('Number added successfully!');
|
87 |
+
closeModal();
|
88 |
+
} else {
|
89 |
+
alert('Error: ' + result.message);
|
90 |
+
}
|
91 |
+
} catch (error) {
|
92 |
+
alert('Error: ' + error.message);
|
93 |
+
}
|
94 |
+
}
|
95 |
+
|
96 |
+
document.getElementById('addNumberForm').addEventListener('submit', (e) => {
|
97 |
+
e.preventDefault();
|
98 |
+
addNumberToGroup();
|
99 |
+
});
|
100 |
+
|
101 |
+
function addAccount() {
|
102 |
+
const accountId = document.getElementById('newAccountId').value;
|
103 |
+
if (!accountId) return;
|
104 |
+
|
105 |
+
fetch('/add-account', {
|
106 |
+
method: 'POST',
|
107 |
+
headers: { 'Content-Type': 'application/json' },
|
108 |
+
body: JSON.stringify({ accountId })
|
109 |
+
})
|
110 |
+
.then(response => response.json())
|
111 |
+
.then(data => {
|
112 |
+
if (data.success) {
|
113 |
+
location.reload();
|
114 |
+
} else {
|
115 |
+
alert(data.error || 'Error adding account');
|
116 |
+
}
|
117 |
+
});
|
118 |
+
}
|
119 |
+
|
120 |
+
function startAccount(accountId) {
|
121 |
+
fetch('/start/' + accountId, { method: 'POST' })
|
122 |
+
.then(response => response.json())
|
123 |
+
.then(data => {
|
124 |
+
if (!data.success) {
|
125 |
+
alert('Error: ' + (data.error || 'Unknown error'));
|
126 |
+
}
|
127 |
+
});
|
128 |
+
}
|
129 |
+
|
130 |
+
function refreshAccounts() {
|
131 |
+
fetch('/accounts-status')
|
132 |
+
.then(response => response.json())
|
133 |
+
.then(data => {
|
134 |
+
const accountsContainer = document.getElementById('accounts');
|
135 |
+
accountsContainer.innerHTML = data.accounts
|
136 |
+
.map(account => generateAccountCard(account.id, account))
|
137 |
+
.join('');
|
138 |
+
});
|
139 |
+
}
|
140 |
+
|
141 |
+
// setInterval(refreshAccounts, 5000);
|
142 |
+
|
143 |
+
|
144 |
+
function toggleDetails(accountId) {
|
145 |
+
const detailsSection = document.getElementById(`details-${accountId}`);
|
146 |
+
const button = detailsSection.previousElementSibling;
|
147 |
+
|
148 |
+
if (detailsSection.classList.contains('hidden')) {
|
149 |
+
detailsSection.classList.remove('hidden');
|
150 |
+
button.textContent = 'Hide Details';
|
151 |
+
} else {
|
152 |
+
detailsSection.classList.add('hidden');
|
153 |
+
button.textContent = 'Show Details';
|
154 |
+
}
|
155 |
+
|
156 |
+
// Update the manager state (optional, depending on how you want to store the open/close state)
|
157 |
+
const accountCard = document.querySelector(`[data-account-id='${accountId}']`);
|
158 |
+
const manager = accountCard.dataset.manager; // You'll need to handle the state persistence
|
159 |
+
manager.processDetailsOpen = !manager.processDetailsOpen;
|
160 |
+
}
|
161 |
+
</script>
|
162 |
+
</body>
|
163 |
+
</html>
|