Mohammed Foud commited on
Commit
ca368c8
·
1 Parent(s): f12461b
corrected_sql_statements.sql ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- WeBook Event URL Input
2
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
3
+ ('webook_enter_event_url',
4
+ '🔗 يرجى إرسال رابط الحدث الذي تريد حجز التذاكر له (مثال: https://webook.com/en/events/mdlbeast-beast-house-ec/book)',
5
+ '🔗 Please send the event URL you want to book tickets for (e.g. https://webook.com/en/events/mdlbeast-beast-house-ec/book)',
6
+ 'Message asking user to enter WeBook event URL');
7
+
8
+ -- Invalid URL
9
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
10
+ ('webook_invalid_url',
11
+ '❌ رابط الحدث غير صحيح. يرجى إرسال رابط صحيح لحدث WeBook.',
12
+ '❌ Invalid event URL. Please send a valid WeBook event URL.',
13
+ 'Error message for invalid WeBook event URL');
14
+
15
+ -- URL Accepted
16
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
17
+ ('webook_url_accepted',
18
+ '✅ تم قبول الرابط!
19
+
20
+ 📍 الرابط الأصلي: {original_url}
21
+ 🔗 الرابط المصحح: {fixed_url}
22
+
23
+ كم عدد التذاكر التي تريد حجزها؟ (يرجى إدخال رقم)',
24
+ '✅ URL accepted!
25
+
26
+ 📍 Original URL: {original_url}
27
+ 🔗 Fixed URL: {fixed_url}
28
+
29
+ How many tickets do you want to book? (Please enter a number)',
30
+ 'Message confirming URL acceptance and asking for ticket count');
31
+
32
+ -- Invalid Ticket Count
33
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
34
+ ('webook_invalid_ticket_count',
35
+ '❌ يرجى إدخال عدد صحيح من التذاكر.',
36
+ '❌ Please enter a valid number of tickets.',
37
+ 'Error message for invalid ticket count');
38
+
39
+ -- No Tickets Available
40
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
41
+ ('webook_no_tickets_available',
42
+ '❌ لا توجد تذاكر متاحة حالياً لهذا الحدث:
43
+
44
+ 📍 رابط الحدث: {event_url}
45
+ 🎫 التذاكر المطلوبة: {tickets_requested}
46
+
47
+ يرجى المحاولة مرة أخرى لاحقاً.',
48
+ '❌ No tickets are currently available for this event:
49
+
50
+ 📍 Event URL: {event_url}
51
+ 🎫 Tickets Requested: {tickets_requested}
52
+
53
+ Please try again later.',
54
+ 'Message when no tickets are available for the event');
55
+
56
+ -- Booking Summary
57
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
58
+ ('webook_booking_summary',
59
+ '📋 ملخص الحجز:
60
+
61
+ 📍 رابط الحدث: {event_url}
62
+ 🎫 التذاكر المطلوبة: {tickets_requested}
63
+ 📊 الحد الأقصى للتذاكر لكل حساب: {max_tickets_per_account}
64
+
65
+ ',
66
+ '📋 Booking Summary:
67
+
68
+ 📍 Event URL: {event_url}
69
+ 🎫 Tickets Requested: {tickets_requested}
70
+ 📊 Max Tickets Per Account: {max_tickets_per_account}
71
+
72
+ ',
73
+ 'Booking summary message before payment question');
74
+
75
+ -- Payment Question
76
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
77
+ ('webook_payment_question',
78
+ 'هل تريد إجراء الدفع تلقائياً؟',
79
+ 'Do you want to make payment automatically?',
80
+ 'Question asking if user wants automatic payment');
81
+
82
+ -- Request Expired
83
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
84
+ ('webook_request_expired',
85
+ 'انتهت صلاحية طلبك أو أنه غير صحيح. يرجى البدء من جديد.',
86
+ 'Your request expired or is invalid. Please start again.',
87
+ 'Message when booking request has expired');
88
+
89
+ -- Processing Messages
90
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
91
+ ('webook_processing_payment',
92
+ '💳 يرجى الانتظار بينما نعالج حجزك والدفع...',
93
+ '💳 Please wait while we process your booking and payment...',
94
+ 'Message when processing booking with automatic payment'),
95
+ ('webook_processing_manual',
96
+ '📝 حجز تذاكرك بدون دفع تلقائي...',
97
+ '📝 Booking your tickets without automatic payment...',
98
+ 'Message when processing booking without automatic payment');
99
+
100
+ -- Account Related Messages
101
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
102
+ ('webook_error_fetching_accounts',
103
+ 'خطأ في جلب الحسابات للحجز.',
104
+ 'Error fetching accounts for booking.',
105
+ 'Error message when failing to fetch accounts'),
106
+ ('webook_no_available_accounts',
107
+ 'لا توجد حسابات نشطة متاحة مع بيانات الاعتماد للحجز.',
108
+ 'No active accounts with credentials available for booking.',
109
+ 'Message when no accounts are available for booking');
110
+
111
+ -- Payment Types
112
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
113
+ ('webook_automatic_payment',
114
+ '💳 الدفع التلقائي',
115
+ '💳 Automatic Payment',
116
+ 'Label for automatic payment type'),
117
+ ('webook_manual_payment',
118
+ '📝 الدفع اليدوي',
119
+ '📝 Manual Payment',
120
+ 'Label for manual payment type');
121
+
122
+ -- Starting Process
123
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
124
+ ('webook_starting_process',
125
+ '🚀 بدء عملية الحجز:
126
+
127
+ 📍 راب�� الحدث: {event_url}
128
+ 🔗 الرابط المصحح: {fixed_url}
129
+ 🎫 التذاكر المطلوبة: {tickets_requested}
130
+ 💳 نوع الدفع: {payment_type}
131
+ 👥 الحسابات المتاحة: {available_accounts}
132
+ ⚡ وضع المعالجة: متوازي ({max_concurrent} حسابات في وقت واحد)
133
+
134
+ معالجة الحسابات بالتوازي للحجز الأسرع...',
135
+ '🚀 Starting booking process:
136
+
137
+ 📍 Event URL: {event_url}
138
+ 🔗 Fixed URL: {fixed_url}
139
+ 🎫 Tickets Requested: {tickets_requested}
140
+ 💳 Payment Type: {payment_type}
141
+ 👥 Available Accounts: {available_accounts}
142
+ ⚡ Processing Mode: Parallel ({max_concurrent} accounts at once)
143
+
144
+ Processing accounts in parallel for faster booking...',
145
+ 'Message when starting the booking process');
146
+
147
+ -- Booking Status Messages
148
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
149
+ ('webook_all_tickets_booked',
150
+ 'تم حجز جميع التذاكر المطلوبة.',
151
+ 'All required tickets have been booked.',
152
+ 'Message when all required tickets are booked'),
153
+ ('webook_logging_in',
154
+ '🔐 تسجيل الدخول بالحساب: {email}',
155
+ '🔐 Logging in with account: {email}',
156
+ 'Message when logging in with an account'),
157
+ ('webook_login_failed',
158
+ '❌ فشل تسجيل الدخول بالحساب: {email}. جاري تجربة الحساب التالي...',
159
+ '❌ Login failed for account: {email}. Trying next account...',
160
+ 'Message when login fails for an account'),
161
+ ('webook_attempting_booking',
162
+ '🎯 محاولة حجز التذاكر مع: {email}',
163
+ '🎯 Attempting to book tickets with: {email}',
164
+ 'Message when attempting to book with an account');
165
+
166
+ -- Booking Success Messages
167
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
168
+ ('webook_booking_success_auto',
169
+ '✅ تم حجز {tickets} تذاكر بنجاح مع {email} (الدفع التلقائي)
170
+ 📊 إجمالي التذاكر المحجوزة: {total_obtained}/{total_needed}',
171
+ '✅ Successfully booked {tickets} tickets with {email} (Auto Payment)
172
+ 📊 Total tickets obtained: {total_obtained}/{total_needed}',
173
+ 'Message when automatic booking is successful'),
174
+ ('webook_booking_success_manual',
175
+ '✅ تم حجز {tickets} تذاكر بنجاح مع {email} (الدفع اليدوي)
176
+ 📊 إجمالي التذاكر المحجوزة: {total_obtained}/{total_needed}',
177
+ '✅ Successfully booked {tickets} tickets with {email} (Manual Payment)
178
+ 📊 Total tickets obtained: {total_obtained}/{total_needed}',
179
+ 'Message when manual booking is successful'),
180
+ ('webook_booking_success_manual_with_payment',
181
+ '🎟️ تم حجز {tickets} تذاكر بنجاح مع {email} (الدفع اليدوي)!
182
+
183
+ 📊 إجمالي التذاكر المحجوزة: {total_obtained}/{total_needed}
184
+
185
+ لإكمال هذا الحجز، يرجى إنهاء الدفع:',
186
+ '🎟️ Successfully booked {tickets} tickets with {email} (Manual Payment)!
187
+
188
+ 📊 Total tickets obtained: {total_obtained}/{total_needed}
189
+
190
+ To complete this booking, please finish the payment:',
191
+ 'Message when manual booking is successful with payment URL');
192
+
193
+ -- Booking Failure Messages
194
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
195
+ ('webook_booking_failed',
196
+ '❌ لا توجد تذاكر متاحة أو فشل الحجز مع الحساب: {email}. جاري تجربة الحساب التالي...',
197
+ '❌ No tickets available or booking failed with account: {email}. Trying next account...',
198
+ 'Message when booking fails with an account'),
199
+ ('webook_tickets_sold_out',
200
+ '❌ نفدت التذاكر للحساب {email}. جاري تجربة الحساب التالي...',
201
+ '❌ Tickets sold out for account {email}. Trying next account...',
202
+ 'Message when tickets are sold out for an account'),
203
+ ('webook_booking_error',
204
+ 'حدث خطأ أثناء الحجز مع {email}: {error}',
205
+ 'An error occurred while booking with {email}: {error}',
206
+ 'Message when an error occurs during booking');
207
+
208
+ -- Final Results Messages
209
+ INSERT INTO bot_messages (key, ar_value, en_value, description) VALUES
210
+ ('webook_booking_completed',
211
+ '🏁 اكتملت عملية الحجز!
212
+
213
+ 📍 رابط الحدث: {event_url}
214
+ 🔗 الرابط المصحح: {fixed_url}
215
+ 🎫 التذاكر المطلوبة: {tickets_requested}
216
+ ✅ التذاكر المحجوزة: {tickets_obtained}
217
+ 💳 نوع الدفع: {payment_type}
218
+ 👥 الحسابات المستخدمة: {accounts_used}
219
+ ⚡ وضع المعالجة: متوازي ({max_concurrent} حسابات في وقت واحد)',
220
+ '🏁 Booking process completed!
221
+
222
+ 📍 Event URL: {event_url}
223
+ 🔗 Fixed URL: {fixed_url}
224
+ 🎫 Tickets Requested: {tickets_requested}
225
+ ✅ Tickets Obtained: {tickets_obtained}
226
+ 💳 Payment Type: {payment_type}
227
+ 👥 Accounts Used: {accounts_used}
228
+ ⚡ Processing Mode: Parallel ({max_concurrent} accounts at once)',
229
+ 'Final booking completion message'),
230
+ ('webook_payment_buttons_sent',
231
+ '
232
+
233
+ 💳 تم إرسال أزرار الدفع لكل حجز ��اجح أعلاه.',
234
+ '
235
+
236
+ 💳 Payment buttons have been sent for each successful booking above.',
237
+ 'Message about payment buttons being sent'),
238
+ ('webook_manual_payment_required',
239
+ '
240
+
241
+ ⚠️ ستحتاج إلى إكمال الدفع يدوياً.',
242
+ '
243
+
244
+ ⚠️ You will need to complete payment manually.',
245
+ 'Message about manual payment requirement');
src/bots/botManager.ts CHANGED
@@ -15,10 +15,10 @@ const logger = createLogger('BotManager');
15
 
16
  export const BotCommands: { command: string; description: string }[] = [
17
  { command: 'start', description: 'بدء البوت' },
18
- { command: 'help', description: 'يعرض قائمة المساعدة' },
19
- { command: 'about', description: 'معلومات عن البوت' },
20
- { command: 'contact', description: 'تواصل معنا' },
21
- { command: 'balance', description: 'رصيدي' },
22
  { command: 'change_language', description: 'تغيير اللغة / Change language' },
23
  ];
24
 
 
15
 
16
  export const BotCommands: { command: string; description: string }[] = [
17
  { command: 'start', description: 'بدء البوت' },
18
+ // { command: 'help', description: 'يعرض قائمة المساعدة' },
19
+ // { command: 'about', description: 'معلومات عن البوت' },
20
+ // { command: 'contact', description: 'تواصل معنا' },
21
+ // { command: 'balance', description: 'رصيدي' },
22
  { command: 'change_language', description: 'تغيير اللغة / Change language' },
23
  ];
24
 
src/bots/handlers/webookHandlers.ts CHANGED
@@ -19,7 +19,13 @@ const userStates = new Map<number, {
19
  }>();
20
 
21
  // Add user state for booking by URL
22
- const bookByUrlStates = new Map<number, { step: number, eventUrl?: string, fixedUrl?: string, tickets?: number }>();
 
 
 
 
 
 
23
 
24
  const EVENTS_PER_PAGE = 5;
25
 
@@ -181,6 +187,13 @@ export const handleWeBookClearFiltersAction = async (ctx: BotContext) => {
181
  };
182
 
183
  export const handleWeBookBackToMenuAction = async (ctx: BotContext) => {
 
 
 
 
 
 
 
184
  return {
185
  message: messageManager.getMessage('main_menu_welcome_back').replace('{name}', ctx.from?.first_name || 'User'),
186
  options: getLoggedInMenuKeyboard()
@@ -189,13 +202,26 @@ export const handleWeBookBackToMenuAction = async (ctx: BotContext) => {
189
 
190
  export const handleWeBookBookByUrlAction = async (ctx: BotContext) => {
191
  const telegramId = ctx.from?.id;
192
- if (!telegramId) return { message: 'User not found.', options: getBackToMainMenuButton() };
 
 
193
  bookByUrlStates.set(telegramId, { step: 1 });
194
  console.log('[BookByUrl] Set state for', telegramId, { step: 1 });
195
- return {
196
- message: '🔗 Please send the event URL you want to book tickets for (e.g. https://webook.com/en/events/mdlbeast-beast-house-ec/book)',
197
- options: { parse_mode: 'HTML', ...getBackToMainMenuButton() }
198
- };
 
 
 
 
 
 
 
 
 
 
 
199
  };
200
 
201
 
@@ -366,11 +392,33 @@ export async function handleBookByUrlText(ctx: BotContext) {
366
  if (!state) return false;
367
  const message = ctx.message;
368
  if (!message || typeof message !== 'object' || !('text' in message) || typeof message.text !== 'string') return false;
 
 
 
 
 
 
 
 
369
  if (state.step === 1) {
 
 
 
 
 
 
 
 
 
370
  // Validate and fix URL
371
  const url = message.text.trim();
372
  if (!isValidWeBookEventUrl(url)) {
373
- await ctx.reply('❌ Invalid event URL. Please send a valid WeBook event URL.', getBackToMainMenuButton());
 
 
 
 
 
374
  return true;
375
  }
376
  const fixedUrl = fixWeBookEventUrl(url);
@@ -379,13 +427,37 @@ export async function handleBookByUrlText(ctx: BotContext) {
379
  state.step = 2;
380
  bookByUrlStates.set(telegramId, state);
381
  console.log('[BookByUrl] URL accepted for', telegramId, 'fixedUrl:', fixedUrl);
382
- await ctx.reply('✅ URL accepted. How many tickets do you want to book? (Please enter a number)', getBackToMainMenuButton());
 
 
 
 
 
 
 
 
 
 
383
  return true;
384
  } else if (state.step === 2) {
 
 
 
 
 
 
 
 
 
385
  // Validate ticket count
386
  const count = parseInt(message.text.trim(), 10);
387
  if (isNaN(count) || count <= 0) {
388
- await ctx.reply('❌ Please enter a valid number of tickets.', getBackToMainMenuButton());
 
 
 
 
 
389
  return true;
390
  }
391
  state.tickets = count;
@@ -395,19 +467,30 @@ export async function handleBookByUrlText(ctx: BotContext) {
395
  const maxTicketsPerAccount = await getMaxTicketsAllowed(state.fixedUrl!);
396
 
397
  if (maxTicketsPerAccount === 0) {
398
- await ctx.reply(`❌ No tickets are currently available for this event:\n\n${state.fixedUrl}\n\nPlease try again later.`, getBackToMainMenuButton());
399
- bookByUrlStates.delete(telegramId);
 
 
 
 
 
 
 
 
 
400
  return true;
401
  }
402
 
403
- let infoMsg = `The event allows booking up to <b>${maxTicketsPerAccount}</b> tickets per account.`;
404
- if (count > maxTicketsPerAccount) {
405
- const neededAccounts = Math.ceil(count / maxTicketsPerAccount);
406
- infoMsg += `\n\nYou requested <b>${count}</b> tickets. This means you need <b>${neededAccounts}</b> accounts to book all tickets.`;
407
- }
 
 
408
  // Ask user if they want to make payment automatically
409
- await ctx.reply(
410
- infoMsg + '\n\nDo you want to make payment automatically?',
411
  {
412
  parse_mode: 'HTML',
413
  ...Markup.inlineKeyboard([
@@ -416,17 +499,13 @@ export async function handleBookByUrlText(ctx: BotContext) {
416
  ])
417
  }
418
  );
 
 
419
  return true;
420
  }
421
  return false;
422
  }
423
 
424
- // Handle Yes/No payment buttons
425
- export function setupWeBookBookByUrlPaymentHandlers(bot: any) {
426
- bot.action('webook_book_by_url_payment_yes', (ctx: BotContext) => handleBookingProcess(ctx, true));
427
- bot.action('webook_book_by_url_payment_no', (ctx: BotContext) => handleBookingProcess(ctx, false));
428
- }
429
-
430
  interface WebhookAccount {
431
  id: string;
432
  email: string;
@@ -439,7 +518,7 @@ async function handleBookingProcess(ctx: BotContext, withPayment: boolean) {
439
 
440
  const state = bookByUrlStates.get(telegramId);
441
  if (!state || !state.eventUrl || !state.tickets) {
442
- await ctx.reply('Your request expired or is invalid. Please start again.');
443
  return;
444
  }
445
 
@@ -447,18 +526,19 @@ async function handleBookingProcess(ctx: BotContext, withPayment: boolean) {
447
  bookByUrlStates.set(telegramId, state);
448
 
449
  if (withPayment) {
450
- await ctx.reply('💳 Please wait while we process your booking and payment...');
451
  } else {
452
- await ctx.reply('📝 Booking your tickets without automatic payment...');
453
  }
454
 
455
  const { eventUrl, tickets: ticketsNeeded } = state;
456
  let ticketsObtained = 0;
 
457
 
458
  const { data: accounts, error } = await fetchDataFromTable<WebhookAccount>('account_webhook', 100, 0, { is_active: true });
459
 
460
  if (error || !accounts) {
461
- await ctx.reply('Error fetching accounts for booking.');
462
  bookByUrlStates.delete(telegramId);
463
  return;
464
  }
@@ -466,56 +546,195 @@ async function handleBookingProcess(ctx: BotContext, withPayment: boolean) {
466
  const availableAccounts = accounts.filter((a) => a.id && a.email && a.password);
467
 
468
  if (availableAccounts.length === 0) {
469
- await ctx.reply('No active accounts with credentials available for booking.');
470
  bookByUrlStates.delete(telegramId);
471
  return;
472
  }
473
 
474
- await ctx.reply(`Found ${availableAccounts.length} active accounts. Starting booking for ${ticketsNeeded} tickets.`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
- for (const account of availableAccounts) {
477
  if (ticketsObtained >= ticketsNeeded) {
478
- await ctx.reply('All required tickets have been booked.');
479
  break;
480
  }
481
 
 
 
 
 
 
 
482
  let login: WeBookLogin | undefined;
483
  try {
484
- await ctx.reply(`Using account: ${account.email}`);
485
  login = new WeBookLogin(account.email, account.password, `${account.id}`);
486
  const page = await login.login();
487
 
488
- if (!page) {
489
- await ctx.reply(`Login failed for account: ${account.email}. Trying next account.`);
490
- continue;
491
  }
492
-
 
493
  const booking = new WeBookBooking(page);
494
- const bookedCount = await booking.bookEvent(eventUrl, withPayment);
495
-
496
- if (bookedCount > 0) {
497
- ticketsObtained += bookedCount;
498
- await ctx.reply(`Successfully booked ${bookedCount} tickets with ${account.email}. Total so far: ${ticketsObtained}.`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  } else {
500
- await ctx.reply(`Booking failed with account: ${account.email}.`);
501
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  } catch (e: any) {
503
- await ctx.reply(`An error occurred while booking with ${account.email}: ${e.message}`);
 
 
 
 
 
504
  } finally {
505
  if (login) {
506
  await login.close();
507
  }
508
  }
 
 
 
 
 
 
 
 
 
 
509
  }
510
 
511
- let finalMessage = `Booking process finished. Total tickets obtained: ${ticketsObtained} out of ${ticketsNeeded} requested.`;
512
- if (!withPayment && ticketsObtained > 0) {
513
- finalMessage += ' You will need to complete payment manually.';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
  }
515
- await ctx.reply(finalMessage);
516
  bookByUrlStates.delete(telegramId);
517
  }
518
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  // Helper: Validate WeBook event URL
520
  function isValidWeBookEventUrl(url: string): boolean {
521
  try {
@@ -536,28 +755,30 @@ function fixWeBookEventUrl(url: string): string {
536
 
537
  // Real implementation: get max tickets allowed (clickCount) using Playwright
538
  async function getMaxTicketsAllowed(eventUrl: string): Promise<number> {
 
539
  const email = process.env.WEBOOK_EMAIL || 'mfoud444@gmail.com';
540
  const password = process.env.WEBOOK_PASSWORD || '009988Ppooii@@@@';
541
  try {
542
- // Login and get a Playwright page
543
- const login = new WeBookLogin(email, password);
544
- const page = await login.login();
545
- if (!page) throw new Error('Login failed');
546
- // Use WeBookBooking to navigate and count max tickets
547
- const booking = new WeBookBooking(page);
548
- await page.goto(eventUrl);
 
549
 
550
- // Check if tickets are available first
551
- if (!(await booking.areTicketsAvailable())) {
552
- console.warn('No tickets available for this event');
553
- if (page.context()) await page.context().close();
554
- return 0;
555
- }
556
 
557
- // Use the selectMaxTickets method and return the clickCount
558
- const clickCount = await booking.selectMaxTickets();
559
- if (page.context()) await page.context().close();
560
- return clickCount;
561
  } catch (e) {
562
  console.error('Error in getMaxTicketsAllowed:', e);
563
  return 5; // fallback
 
19
  }>();
20
 
21
  // Add user state for booking by URL
22
+ const bookByUrlStates = new Map<number, {
23
+ step: number,
24
+ eventUrl?: string,
25
+ fixedUrl?: string,
26
+ tickets?: number,
27
+ lastBotMessageId?: number
28
+ }>();
29
 
30
  const EVENTS_PER_PAGE = 5;
31
 
 
187
  };
188
 
189
  export const handleWeBookBackToMenuAction = async (ctx: BotContext) => {
190
+ // Delete the previous message with buttons
191
+ try {
192
+ await ctx.deleteMessage();
193
+ } catch (e) {
194
+ console.log('Could not delete previous message:', e);
195
+ }
196
+
197
  return {
198
  message: messageManager.getMessage('main_menu_welcome_back').replace('{name}', ctx.from?.first_name || 'User'),
199
  options: getLoggedInMenuKeyboard()
 
202
 
203
  export const handleWeBookBookByUrlAction = async (ctx: BotContext) => {
204
  const telegramId = ctx.from?.id;
205
+ if (!telegramId) return { message: messageManager.getMessage('error_user_not_found'), options: getBackToMainMenuButton() };
206
+
207
+ // Initialize state and send message directly
208
  bookByUrlStates.set(telegramId, { step: 1 });
209
  console.log('[BookByUrl] Set state for', telegramId, { step: 1 });
210
+
211
+ const reply = await ctx.reply(
212
+ messageManager.getMessage('webook_enter_event_url'),
213
+ { parse_mode: 'HTML', ...getBackToMainMenuButton() }
214
+ );
215
+
216
+ // Store the message ID for later deletion
217
+ const state = bookByUrlStates.get(telegramId);
218
+ if (state) {
219
+ state.lastBotMessageId = reply.message_id;
220
+ bookByUrlStates.set(telegramId, state);
221
+ }
222
+
223
+ // Return null to prevent callbackReplyHandler from sending another message
224
+ return null;
225
  };
226
 
227
 
 
392
  if (!state) return false;
393
  const message = ctx.message;
394
  if (!message || typeof message !== 'object' || !('text' in message) || typeof message.text !== 'string') return false;
395
+
396
+ // Delete the user's message to clean up the chat
397
+ try {
398
+ await ctx.deleteMessage();
399
+ } catch (e) {
400
+ console.log('Could not delete user message:', e);
401
+ }
402
+
403
  if (state.step === 1) {
404
+ // Delete the bot's previous message asking for URL
405
+ if (state.lastBotMessageId) {
406
+ try {
407
+ await ctx.deleteMessage(state.lastBotMessageId);
408
+ } catch (e) {
409
+ console.log('Could not delete bot message:', e);
410
+ }
411
+ }
412
+
413
  // Validate and fix URL
414
  const url = message.text.trim();
415
  if (!isValidWeBookEventUrl(url)) {
416
+ const reply = await ctx.reply(messageManager.getMessage('webook_invalid_url'), {
417
+ parse_mode: 'HTML',
418
+ ...getBackToMainMenuButton()
419
+ });
420
+ state.lastBotMessageId = reply.message_id;
421
+ bookByUrlStates.set(telegramId, state);
422
  return true;
423
  }
424
  const fixedUrl = fixWeBookEventUrl(url);
 
427
  state.step = 2;
428
  bookByUrlStates.set(telegramId, state);
429
  console.log('[BookByUrl] URL accepted for', telegramId, 'fixedUrl:', fixedUrl);
430
+ const reply = await ctx.reply(
431
+ messageManager.getMessage('webook_url_accepted')
432
+ .replace('{original_url}', url)
433
+ .replace('{fixed_url}', fixedUrl),
434
+ {
435
+ parse_mode: 'HTML',
436
+ ...getBackToMainMenuButton()
437
+ }
438
+ );
439
+ state.lastBotMessageId = reply.message_id;
440
+ bookByUrlStates.set(telegramId, state);
441
  return true;
442
  } else if (state.step === 2) {
443
+ // Delete the bot's previous message asking for ticket count
444
+ if (state.lastBotMessageId) {
445
+ try {
446
+ await ctx.deleteMessage(state.lastBotMessageId);
447
+ } catch (e) {
448
+ console.log('Could not delete bot message:', e);
449
+ }
450
+ }
451
+
452
  // Validate ticket count
453
  const count = parseInt(message.text.trim(), 10);
454
  if (isNaN(count) || count <= 0) {
455
+ const reply = await ctx.reply(messageManager.getMessage('webook_invalid_ticket_count'), {
456
+ parse_mode: 'HTML',
457
+ ...getBackToMainMenuButton()
458
+ });
459
+ state.lastBotMessageId = reply.message_id;
460
+ bookByUrlStates.set(telegramId, state);
461
  return true;
462
  }
463
  state.tickets = count;
 
467
  const maxTicketsPerAccount = await getMaxTicketsAllowed(state.fixedUrl!);
468
 
469
  if (maxTicketsPerAccount === 0) {
470
+ const reply = await ctx.reply(
471
+ messageManager.getMessage('webook_no_tickets_available')
472
+ .replace('{event_url}', state.fixedUrl!)
473
+ .replace('{tickets_requested}', count.toString()),
474
+ {
475
+ parse_mode: 'HTML',
476
+ ...getBackToMainMenuButton()
477
+ }
478
+ );
479
+ state.lastBotMessageId = reply.message_id;
480
+ bookByUrlStates.set(telegramId, state);
481
  return true;
482
  }
483
 
484
+ let infoMsg = messageManager.getMessage('webook_booking_summary')
485
+ .replace('{event_url}', state.fixedUrl!)
486
+ .replace('{tickets_requested}', count.toString())
487
+ .replace('{max_tickets_per_account}', maxTicketsPerAccount.toString());
488
+
489
+ infoMsg += messageManager.getMessage('webook_payment_question');
490
+
491
  // Ask user if they want to make payment automatically
492
+ const reply = await ctx.reply(
493
+ infoMsg,
494
  {
495
  parse_mode: 'HTML',
496
  ...Markup.inlineKeyboard([
 
499
  ])
500
  }
501
  );
502
+ state.lastBotMessageId = reply.message_id;
503
+ bookByUrlStates.set(telegramId, state);
504
  return true;
505
  }
506
  return false;
507
  }
508
 
 
 
 
 
 
 
509
  interface WebhookAccount {
510
  id: string;
511
  email: string;
 
518
 
519
  const state = bookByUrlStates.get(telegramId);
520
  if (!state || !state.eventUrl || !state.tickets) {
521
+ await ctx.reply(messageManager.getMessage('webook_request_expired'), { parse_mode: 'HTML' });
522
  return;
523
  }
524
 
 
526
  bookByUrlStates.set(telegramId, state);
527
 
528
  if (withPayment) {
529
+ await ctx.reply(messageManager.getMessage('webook_processing_payment'), { parse_mode: 'HTML' });
530
  } else {
531
+ await ctx.reply(messageManager.getMessage('webook_processing_manual'), { parse_mode: 'HTML' });
532
  }
533
 
534
  const { eventUrl, tickets: ticketsNeeded } = state;
535
  let ticketsObtained = 0;
536
+ const paymentUrls: string[] = [];
537
 
538
  const { data: accounts, error } = await fetchDataFromTable<WebhookAccount>('account_webhook', 100, 0, { is_active: true });
539
 
540
  if (error || !accounts) {
541
+ await ctx.reply(messageManager.getMessage('webook_error_fetching_accounts'), { parse_mode: 'HTML' });
542
  bookByUrlStates.delete(telegramId);
543
  return;
544
  }
 
546
  const availableAccounts = accounts.filter((a) => a.id && a.email && a.password);
547
 
548
  if (availableAccounts.length === 0) {
549
+ await ctx.reply(messageManager.getMessage('webook_no_available_accounts'), { parse_mode: 'HTML' });
550
  bookByUrlStates.delete(telegramId);
551
  return;
552
  }
553
 
554
+ const paymentType = withPayment ? messageManager.getMessage('webook_automatic_payment') : messageManager.getMessage('webook_manual_payment');
555
+
556
+ // Process accounts in parallel with a limit to avoid overwhelming the system
557
+ const MAX_CONCURRENT_ACCOUNTS = 3; // Adjust this number based on your system capacity
558
+
559
+ await ctx.reply(
560
+ messageManager.getMessage('webook_starting_process')
561
+ .replace('{event_url}', eventUrl)
562
+ .replace('{fixed_url}', state.fixedUrl || eventUrl)
563
+ .replace('{tickets_requested}', ticketsNeeded.toString())
564
+ .replace('{payment_type}', paymentType)
565
+ .replace('{available_accounts}', availableAccounts.length.toString())
566
+ .replace('{max_concurrent}', MAX_CONCURRENT_ACCOUNTS.toString()),
567
+ { parse_mode: 'HTML' }
568
+ );
569
+
570
+ const accountChunks = [];
571
+
572
+ for (let i = 0; i < availableAccounts.length; i += MAX_CONCURRENT_ACCOUNTS) {
573
+ accountChunks.push(availableAccounts.slice(i, i + MAX_CONCURRENT_ACCOUNTS));
574
+ }
575
 
576
+ for (const accountChunk of accountChunks) {
577
  if (ticketsObtained >= ticketsNeeded) {
578
+ await ctx.reply(messageManager.getMessage('webook_all_tickets_booked'), { parse_mode: 'HTML' });
579
  break;
580
  }
581
 
582
+ // Process this chunk of accounts in parallel
583
+ const bookingPromises = accountChunk.map(async (account) => {
584
+ if (ticketsObtained >= ticketsNeeded) {
585
+ return null; // Skip if we already have enough tickets
586
+ }
587
+
588
  let login: WeBookLogin | undefined;
589
  try {
590
+ await ctx.reply(messageManager.getMessage('webook_logging_in').replace('{email}', account.email), { parse_mode: 'HTML' });
591
  login = new WeBookLogin(account.email, account.password, `${account.id}`);
592
  const page = await login.login();
593
 
594
+ if (!page || typeof page !== 'object') {
595
+ await ctx.reply(messageManager.getMessage('webook_login_failed').replace('{email}', account.email), { parse_mode: 'HTML' });
596
+ return null;
597
  }
598
+
599
+ await ctx.reply(messageManager.getMessage('webook_attempting_booking').replace('{email}', account.email), { parse_mode: 'HTML' });
600
  const booking = new WeBookBooking(page);
601
+ const bookingResult = await booking.bookEvent(eventUrl, withPayment);
602
+
603
+ if (withPayment) {
604
+ // Handle number result for automatic payment
605
+ if (typeof bookingResult === 'number') {
606
+ const bookedCount = bookingResult;
607
+ if (bookedCount > 0) {
608
+ ticketsObtained += bookedCount;
609
+ await ctx.reply(messageManager.getMessage('webook_booking_success_auto')
610
+ .replace('{tickets}', bookedCount.toString())
611
+ .replace('{email}', account.email)
612
+ .replace('{total_obtained}', ticketsObtained.toString())
613
+ .replace('{total_needed}', ticketsNeeded.toString()), { parse_mode: 'HTML' });
614
+ return { success: true, tickets: bookedCount, account: account.email };
615
+ } else {
616
+ await ctx.reply(messageManager.getMessage('webook_booking_failed').replace('{email}', account.email), { parse_mode: 'HTML' });
617
+ return { success: false, account: account.email };
618
+ }
619
+ }
620
  } else {
621
+ console.log("bookingResult", bookingResult)
622
+ // Handle object result for manual payment
623
+ if (typeof bookingResult === 'object' && bookingResult !== null && 'ticketsCount' in bookingResult) {
624
+ const result = bookingResult as { ticketsCount: number, paymentUrl?: string };
625
+ if (result.ticketsCount > 0) {
626
+ ticketsObtained += result.ticketsCount;
627
+ if (result.paymentUrl) {
628
+ paymentUrls.push(result.paymentUrl);
629
+
630
+ // Send payment button immediately after successful booking
631
+ await ctx.reply(
632
+ messageManager.getMessage('webook_booking_success_manual_with_payment')
633
+ .replace('{tickets}', result.ticketsCount.toString())
634
+ .replace('{email}', account.email)
635
+ .replace('{total_obtained}', ticketsObtained.toString())
636
+ .replace('{total_needed}', ticketsNeeded.toString()),
637
+ {
638
+ parse_mode: 'HTML',
639
+ ...Markup.inlineKeyboard([
640
+ [Markup.button.url('Complete Payment', result.paymentUrl)],
641
+ [Markup.button.callback('🔙 Back to Menu', 'webook_back_to_menu')]
642
+ ])
643
+ }
644
+ );
645
+ }
646
+ await ctx.reply(messageManager.getMessage('webook_booking_success_manual')
647
+ .replace('{tickets}', result.ticketsCount.toString())
648
+ .replace('{email}', account.email)
649
+ .replace('{total_obtained}', ticketsObtained.toString())
650
+ .replace('{total_needed}', ticketsNeeded.toString()), { parse_mode: 'HTML' });
651
+ return { success: true, tickets: result.ticketsCount, account: account.email, paymentUrl: result.paymentUrl };
652
+ } else {
653
+ await ctx.reply(messageManager.getMessage('webook_booking_failed').replace('{email}', account.email), { parse_mode: 'HTML' });
654
+ return { success: false, account: account.email };
655
+ }
656
+ }
657
+ }
658
+ return { success: false, account: account.email };
659
  } catch (e: any) {
660
+ if (e.message && e.message.includes('Tickets sold out')) {
661
+ await ctx.reply(messageManager.getMessage('webook_tickets_sold_out').replace('{email}', account.email), { parse_mode: 'HTML' });
662
+ } else {
663
+ await ctx.reply(messageManager.getMessage('webook_booking_error').replace('{email}', account.email).replace('{error}', e.message), { parse_mode: 'HTML' });
664
+ }
665
+ return { success: false, account: account.email, error: e.message };
666
  } finally {
667
  if (login) {
668
  await login.close();
669
  }
670
  }
671
+ });
672
+
673
+ // Wait for all accounts in this chunk to complete
674
+ const results = await Promise.all(bookingPromises);
675
+
676
+ // Check if we have enough tickets
677
+ if (ticketsObtained >= ticketsNeeded) {
678
+ await ctx.reply(messageManager.getMessage('webook_all_tickets_booked'), { parse_mode: 'HTML' });
679
+ break;
680
+ }
681
  }
682
 
683
+ // Show final results
684
+ let finalMessage = messageManager.getMessage('webook_booking_completed')
685
+ .replace('{event_url}', eventUrl)
686
+ .replace('{fixed_url}', state.fixedUrl || eventUrl)
687
+ .replace('{tickets_requested}', ticketsNeeded.toString())
688
+ .replace('{tickets_obtained}', ticketsObtained.toString())
689
+ .replace('{payment_type}', paymentType)
690
+ .replace('{accounts_used}', availableAccounts.length.toString())
691
+ .replace('{max_concurrent}', MAX_CONCURRENT_ACCOUNTS.toString());
692
+
693
+ if (!withPayment && ticketsObtained > 0 && paymentUrls.length > 0) {
694
+ finalMessage += messageManager.getMessage('webook_payment_buttons_sent');
695
+
696
+ await ctx.reply(
697
+ finalMessage,
698
+ {
699
+ parse_mode: 'HTML',
700
+ ...Markup.inlineKeyboard([
701
+ [Markup.button.callback('🔙 Back to Menu', 'webook_back_to_menu')]
702
+ ])
703
+ }
704
+ );
705
+ } else if (!withPayment && ticketsObtained > 0) {
706
+ finalMessage += messageManager.getMessage('webook_manual_payment_required');
707
+ await ctx.reply(finalMessage, { parse_mode: 'HTML' });
708
+ } else {
709
+ await ctx.reply(finalMessage, { parse_mode: 'HTML' });
710
  }
711
+
712
  bookByUrlStates.delete(telegramId);
713
  }
714
 
715
+ // Handle Yes/No payment buttons
716
+ export function setupWeBookBookByUrlPaymentHandlers(bot: any) {
717
+ bot.action('webook_book_by_url_payment_yes', async (ctx: BotContext) => {
718
+ // Delete the previous message with buttons
719
+ try {
720
+ await ctx.deleteMessage();
721
+ } catch (e) {
722
+ console.log('Could not delete previous message:', e);
723
+ }
724
+ await handleBookingProcess(ctx, true);
725
+ });
726
+
727
+ bot.action('webook_book_by_url_payment_no', async (ctx: BotContext) => {
728
+ // Delete the previous message with buttons
729
+ try {
730
+ await ctx.deleteMessage();
731
+ } catch (e) {
732
+ console.log('Could not delete previous message:', e);
733
+ }
734
+ await handleBookingProcess(ctx, false);
735
+ });
736
+ }
737
+
738
  // Helper: Validate WeBook event URL
739
  function isValidWeBookEventUrl(url: string): boolean {
740
  try {
 
755
 
756
  // Real implementation: get max tickets allowed (clickCount) using Playwright
757
  async function getMaxTicketsAllowed(eventUrl: string): Promise<number> {
758
+
759
  const email = process.env.WEBOOK_EMAIL || 'mfoud444@gmail.com';
760
  const password = process.env.WEBOOK_PASSWORD || '009988Ppooii@@@@';
761
  try {
762
+ return 5;
763
+ // // Login and get a Playwright page
764
+ // const login = new WeBookLogin(email, password);
765
+ // const page = await login.login();
766
+ // if (!page) throw new Error('Login failed');
767
+ // // Use WeBookBooking to navigate and count max tickets
768
+ // const booking = new WeBookBooking(page);
769
+ // await page.goto(eventUrl);
770
 
771
+ // // Check if tickets are available first
772
+ // if (!(await booking.areTicketsAvailable())) {
773
+ // console.warn('No tickets available for this event');
774
+ // if (page.context()) await page.context().close();
775
+ // return 0;
776
+ // }
777
 
778
+ // // Use the selectMaxTickets method and return the clickCount
779
+ // const clickCount = await booking.selectMaxTickets();
780
+ // if (page.context()) await page.context().close();
781
+ // return clickCount;
782
  } catch (e) {
783
  console.error('Error in getMaxTicketsAllowed:', e);
784
  return 5; // fallback
src/bots/utils/handlerUtils.ts CHANGED
@@ -53,7 +53,7 @@ export const messageReplyHandler = (
53
  * @returns A function that handles the callback with proper error handling
54
  */
55
  export const callbackReplyHandler = (
56
- replyFunction: (ctx: BotContext) => Promise<{message: string, options?: any}> | {message: string, options?: any}
57
  ) => async (ctx: BotContext) => {
58
  try {
59
  // Answer the callback query first
@@ -63,6 +63,11 @@ export const callbackReplyHandler = (
63
  // Execute the reply function to get message content and options
64
  const result = await Promise.resolve(replyFunction(ctx));
65
 
 
 
 
 
 
66
  // Send the reply with the message and any provided options
67
  return await ctx.reply(result.message, result.options);
68
  } catch (error: any) {
 
53
  * @returns A function that handles the callback with proper error handling
54
  */
55
  export const callbackReplyHandler = (
56
+ replyFunction: (ctx: BotContext) => Promise<{message: string, options?: any}> | {message: string, options?: any} | null
57
  ) => async (ctx: BotContext) => {
58
  try {
59
  // Answer the callback query first
 
63
  // Execute the reply function to get message content and options
64
  const result = await Promise.resolve(replyFunction(ctx));
65
 
66
+ // If result is null or undefined, don't send any message
67
+ if (!result) {
68
+ return;
69
+ }
70
+
71
  // Send the reply with the message and any provided options
72
  return await ctx.reply(result.message, result.options);
73
  } catch (error: any) {
src/bots/utils/keyboardUtils.ts CHANGED
@@ -14,9 +14,9 @@ export const getMainMenuKeyboard = () => {
14
  // [Markup.button.callback(messageManager.getMessage('btn_login'), 'login')],
15
  // [Markup.button.callback(messageManager.getMessage('btn_terms'), 'terms')],
16
  [Markup.button.callback('🎭 WeBook Events', 'webook_events'),
17
- Markup.button.callback('🎟️ Book Tickets by Event URL', 'webook_book_by_url')
18
  ],
19
- // [Markup.button.callback('🎟️ Book Tickets by Event URL', 'webook_book_by_url')],
20
  // [Markup.button.callback(messageManager.getMessage('btn_new_members'), 'new_members')],
21
  // [Markup.button.callback(messageManager.getMessage('btn_stats'), 'stats')],
22
  [Markup.button.callback(messageManager.getMessage('btn_change_language'), 'change_language')],
@@ -25,16 +25,17 @@ export const getMainMenuKeyboard = () => {
25
 
26
  export const getLoggedInMenuKeyboard = () => {
27
  return Markup.inlineKeyboard([
28
- [Markup.button.callback('🔍 Browse Services', 'browse_services')],
29
  [Markup.button.callback('🎭 WeBook Events', 'webook_events')],
 
30
  [
31
- Markup.button.callback(messageManager.getMessage('btn_profile'), 'profile'),
32
  Markup.button.callback(messageManager.getMessage('btn_change_language'), 'change_language')
33
  ],
34
- [
35
- Markup.button.callback(messageManager.getMessage('btn_top_up'), 'top_up_balance'),
36
- Markup.button.callback(messageManager.getMessage('btn_history'), 'history')
37
- ],
38
  // [Markup.button.callback('💰 Buy with Balance', 'buy_with_balance')],
39
  [
40
  Markup.button.callback(messageManager.getMessage('btn_back'), 'main_menu')
 
14
  // [Markup.button.callback(messageManager.getMessage('btn_login'), 'login')],
15
  // [Markup.button.callback(messageManager.getMessage('btn_terms'), 'terms')],
16
  [Markup.button.callback('🎭 WeBook Events', 'webook_events'),
17
+ // Markup.button.callback('🎟️ Book Tickets by Event URL', 'webook_book_by_url')
18
  ],
19
+ [Markup.button.callback('🎟️ Book Tickets by Event URL', 'webook_book_by_url')],
20
  // [Markup.button.callback(messageManager.getMessage('btn_new_members'), 'new_members')],
21
  // [Markup.button.callback(messageManager.getMessage('btn_stats'), 'stats')],
22
  [Markup.button.callback(messageManager.getMessage('btn_change_language'), 'change_language')],
 
25
 
26
  export const getLoggedInMenuKeyboard = () => {
27
  return Markup.inlineKeyboard([
28
+ // [Markup.button.callback('🔍 Browse Services', 'browse_services')],
29
  [Markup.button.callback('🎭 WeBook Events', 'webook_events')],
30
+ [Markup.button.callback('🎟️ Book Tickets by Event URL', 'webook_book_by_url')],
31
  [
32
+ // Markup.button.callback(messageManager.getMessage('btn_profile'), 'profile'),
33
  Markup.button.callback(messageManager.getMessage('btn_change_language'), 'change_language')
34
  ],
35
+ // [
36
+ // Markup.button.callback(messageManager.getMessage('btn_top_up'), 'top_up_balance'),
37
+ // Markup.button.callback(messageManager.getMessage('btn_history'), 'history')
38
+ // ],
39
  // [Markup.button.callback('💰 Buy with Balance', 'buy_with_balance')],
40
  [
41
  Markup.button.callback(messageManager.getMessage('btn_back'), 'main_menu')
src/bots/utils/messageManager.ts CHANGED
@@ -30,6 +30,11 @@ class MessageManager {
30
 
31
  setLanguage(lang: 'ar' | 'en') {
32
  this.language = lang;
 
 
 
 
 
33
  }
34
 
35
  async loadMessages() {
@@ -142,7 +147,9 @@ class MessageManager {
142
  logger.warn(`Message not found for key: ${key}`);
143
  return `${key}`;
144
  }
145
- return this.language === 'ar' ? message.ar_value : message.en_value;
 
 
146
  }
147
 
148
  async updateMessage(key: string, arValue: string, enValue: string, description?: string) {
@@ -170,6 +177,10 @@ class MessageManager {
170
  throw error;
171
  }
172
  }
 
 
 
 
173
  }
174
 
175
  export const messageManager = MessageManager.getInstance();
 
30
 
31
  setLanguage(lang: 'ar' | 'en') {
32
  this.language = lang;
33
+ logger.info(`Language set to: ${lang}`);
34
+ }
35
+
36
+ getLanguage(): 'ar' | 'en' {
37
+ return this.language;
38
  }
39
 
40
  async loadMessages() {
 
147
  logger.warn(`Message not found for key: ${key}`);
148
  return `${key}`;
149
  }
150
+ const value = this.language === 'ar' ? message.ar_value : message.en_value;
151
+ logger.debug(`Retrieved message for key '${key}': ${value.substring(0, 100)}...`);
152
+ return value;
153
  }
154
 
155
  async updateMessage(key: string, arValue: string, enValue: string, description?: string) {
 
177
  throw error;
178
  }
179
  }
180
+
181
+ async reloadMessages() {
182
+ await this.loadMessages();
183
+ }
184
  }
185
 
186
  export const messageManager = MessageManager.getInstance();
src/controllers.ts CHANGED
@@ -416,21 +416,64 @@ export const loginWeBook = async (req: Request, res: Response) => {
416
  if (!id || !email || !password) {
417
  return res.status(400).json({ error: 'id, email, and password are required' });
418
  }
 
419
  try {
420
- // Create profile directory directly under etc/
421
- const profileDir = path.join(process.cwd(), 'etc', id.toString());
422
- const login = new WeBookLogin(email, password, profileDir);
423
  const result = await login.login();
424
- const is_login = !!result;
425
- // Update is_login in account_webhook table
426
- await updateDataInTable('account_webhook', { id, is_login });
427
- return res.json({ state: { is_login } });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  } catch (error: any) {
429
- // On error, set is_login to false
430
  try {
431
  await updateDataInTable('account_webhook', { id, is_login: false });
432
  } catch {}
433
- return res.status(500).json({ error: error.message || 'Login failed', state: { is_login: false } });
 
 
 
 
 
 
 
 
 
 
 
434
  }
435
  };
436
 
@@ -455,11 +498,12 @@ export const signupWeBook = async (req: Request, res: Response) => {
455
  });
456
  }
457
 
 
458
  try {
459
  // Create profile directory for this account
460
- const profileDir = path.join(process.cwd(), 'etc', id.toString());
461
 
462
- const signup = new WeBookSignup({
463
  firstName,
464
  lastName,
465
  email,
@@ -470,21 +514,49 @@ export const signupWeBook = async (req: Request, res: Response) => {
470
  }, profileDir);
471
 
472
  const result = await signup.signup();
473
- const is_login = !!result;
 
 
474
 
475
  // Update account status in database
476
  await updateDataInTable('account_webhook', {
477
  id,
478
- is_login,
 
479
  email,
480
  password // Note: In production, you should hash the password
481
  });
482
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  return sendResponse(res, 200, {
484
  status: 200,
485
  data: {
486
- message: is_login ? 'Signup successful' : 'Signup failed',
487
- is_login: is_login,
 
488
  profileDir
489
  }
490
  });
@@ -493,15 +565,42 @@ export const signupWeBook = async (req: Request, res: Response) => {
493
  try {
494
  await updateDataInTable('account_webhook', {
495
  id,
496
- is_login: false
 
497
  });
498
  } catch {}
499
  return sendResponse(res, 500, {
500
  status: 500,
501
  data: {
502
  message: error.message || 'Signup failed',
503
- is_login: false
 
504
  }
505
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  }
507
  };
 
416
  if (!id || !email || !password) {
417
  return res.status(400).json({ error: 'id, email, and password are required' });
418
  }
419
+ let login: WeBookLogin | undefined;
420
  try {
421
+ login = new WeBookLogin(email, password, id.toString());
 
 
422
  const result = await login.login();
423
+ let is_login = false;
424
+ let message = '';
425
+ if (typeof result === 'string') {
426
+ message = result;
427
+ is_login = false;
428
+ await updateDataInTable('account_webhook', { id, is_login });
429
+ return res.status(200).json({
430
+ status: 200,
431
+ data: {
432
+ message,
433
+ is_login,
434
+ profileDir: id.toString()
435
+ }
436
+ });
437
+ } else if (!result) {
438
+ message = 'Login failed';
439
+ is_login = false;
440
+ await updateDataInTable('account_webhook', { id, is_login });
441
+ return res.status(200).json({
442
+ status: 200,
443
+ data: {
444
+ message,
445
+ is_login,
446
+ profileDir: id.toString()
447
+ }
448
+ });
449
+ } else {
450
+ is_login = true;
451
+ await updateDataInTable('account_webhook', { id, is_login });
452
+ return res.status(200).json({
453
+ status: 200,
454
+ data: {
455
+ message: 'Login successful',
456
+ is_login,
457
+ profileDir: id.toString()
458
+ }
459
+ });
460
+ }
461
  } catch (error: any) {
 
462
  try {
463
  await updateDataInTable('account_webhook', { id, is_login: false });
464
  } catch {}
465
+ return res.status(500).json({
466
+ status: 500,
467
+ data: {
468
+ message: error.message || 'Login failed',
469
+ is_login: false,
470
+ profileDir: id.toString()
471
+ }
472
+ });
473
+ } finally {
474
+ if (login) {
475
+ await login.close();
476
+ }
477
  }
478
  };
479
 
 
498
  });
499
  }
500
 
501
+ let signup: WeBookSignup | undefined;
502
  try {
503
  // Create profile directory for this account
504
+ const profileDir = id.toString();
505
 
506
+ signup = new WeBookSignup({
507
  firstName,
508
  lastName,
509
  email,
 
514
  }, profileDir);
515
 
516
  const result = await signup.signup();
517
+ console.log("jhjgjh", result)
518
+ const is_login = !!result && typeof result !== 'string';
519
+ const is_register = !!result && typeof result !== 'string';
520
 
521
  // Update account status in database
522
  await updateDataInTable('account_webhook', {
523
  id,
524
+ is_login,
525
+ is_register,
526
  email,
527
  password // Note: In production, you should hash the password
528
  });
529
 
530
+ if (typeof result === 'string') {
531
+ return sendResponse(res, 200, {
532
+ status: 200,
533
+ data: {
534
+ message: result,
535
+ is_login: false,
536
+ is_register: false,
537
+ profileDir
538
+ }
539
+ });
540
+ }
541
+
542
+ if (!result) {
543
+ return sendResponse(res, 200, {
544
+ status: 200,
545
+ data: {
546
+ message: 'Signup failed',
547
+ is_login: false,
548
+ is_register: false,
549
+ profileDir
550
+ }
551
+ });
552
+ }
553
+
554
  return sendResponse(res, 200, {
555
  status: 200,
556
  data: {
557
+ message: 'Signup successful',
558
+ is_login: true,
559
+ is_register: true,
560
  profileDir
561
  }
562
  });
 
565
  try {
566
  await updateDataInTable('account_webhook', {
567
  id,
568
+ is_login: false,
569
+ is_register: false
570
  });
571
  } catch {}
572
  return sendResponse(res, 500, {
573
  status: 500,
574
  data: {
575
  message: error.message || 'Signup failed',
576
+ is_login: false,
577
+ is_register: false
578
  }
579
  });
580
+ } finally {
581
+ if (signup) {
582
+ await signup.close();
583
+ }
584
+ }
585
+ };
586
+
587
+ export const checkLoginWeBook = async (req: Request, res: Response) => {
588
+ const { id, email, password } = req.body;
589
+ if (!id || !email || !password) {
590
+ return res.status(400).json({ error: 'id, email, and password are required' });
591
+ }
592
+ try {
593
+ const login = new WeBookLogin(email, password, id.toString());
594
+ const { page } = await login.setupBrowser();
595
+ login.page = page;
596
+ const isLogin = await login.checkAlreadyLoggedIn();
597
+ await updateDataInTable('account_webhook', { id, is_login: isLogin });
598
+ await login.close();
599
+ return res.json({ isLogin });
600
+ } catch (error: any) {
601
+ try {
602
+ await updateDataInTable('account_webhook', { id, is_login: false });
603
+ } catch {}
604
+ return res.status(500).json({ error: error.message || 'Check login failed', isLogin: false });
605
  }
606
  };
src/db/supabaseHelper.ts CHANGED
@@ -115,3 +115,25 @@ export const getImageUrl = async (bucket: string, imageName: string): Promise<st
115
  throw error;
116
  }
117
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  throw error;
116
  }
117
  };
118
+
119
+ // Utility function to fetch all payment methods
120
+ export const fetchAllPaymentMethods = async (): Promise<any[]> => {
121
+ try {
122
+ const { data, error } = await supabase
123
+ .from('payment_methods')
124
+ .select('*')
125
+ .eq('is_active', true)
126
+ .order('is_default', { ascending: false })
127
+ .order('created_at', { ascending: false });
128
+
129
+ if (error) {
130
+ throw error;
131
+ }
132
+
133
+ const camelData = data?.map(snakeToCamel) || [];
134
+ return camelData;
135
+ } catch (error: any) {
136
+ console.error('Error fetching payment methods:', error.message);
137
+ throw error;
138
+ }
139
+ };
src/routes.ts CHANGED
@@ -27,7 +27,8 @@ import {
27
  startSpecificBotEndpoint,
28
  stopSpecificBotEndpoint,
29
  loginWeBook,
30
- signupWeBook
 
31
  } from "./controllers";
32
 
33
  export const setupRoutes = (app: any) => {
@@ -68,6 +69,7 @@ export const setupRoutes = (app: any) => {
68
 
69
  router.post("/login-webook", loginWeBook as unknown as RequestHandler);
70
  router.post("/signup-webook", signupWeBook as unknown as RequestHandler);
 
71
 
72
  app.use("/", router);
73
  };
 
27
  startSpecificBotEndpoint,
28
  stopSpecificBotEndpoint,
29
  loginWeBook,
30
+ signupWeBook,
31
+ checkLoginWeBook
32
  } from "./controllers";
33
 
34
  export const setupRoutes = (app: any) => {
 
69
 
70
  router.post("/login-webook", loginWeBook as unknown as RequestHandler);
71
  router.post("/signup-webook", signupWeBook as unknown as RequestHandler);
72
+ router.post("/check-login-webook", checkLoginWeBook as unknown as RequestHandler);
73
 
74
  app.use("/", router);
75
  };
src/webook/book.ts CHANGED
@@ -1,5 +1,6 @@
1
  import { Page } from 'playwright';
2
  import { WeBookBase } from './webookBase';
 
3
 
4
  export class WeBookBooking extends WeBookBase {
5
  constructor(page: Page, profileDir?: string) {
@@ -9,14 +10,14 @@ export class WeBookBooking extends WeBookBase {
9
  }
10
  }
11
 
12
- async bookEvent(eventUrl: string, withPayment: boolean = true): Promise<number> {
13
  if (!this.page) throw new Error('Page not initialized');
14
  try {
15
  console.info(`Navigating to event booking page: ${eventUrl}`);
16
- this.navigateToEventPage(eventUrl);
17
 
18
  if (!(await this.areTicketsAvailable())) {
19
- return 0;
20
  }
21
 
22
  const ticketsCount = await this.selectMaxTickets();
@@ -25,15 +26,25 @@ export class WeBookBooking extends WeBookBase {
25
  await this.acceptTermsIfAvailable();
26
  await this.confirmAndPay();
27
  await this.waitForSpinnerToDisappear();
 
28
  if (withPayment) {
29
- await this.handlePayTabsPayment();
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
-
32
- // Wait for a while (simulate time.sleep(330))
33
- await new Promise((resolve) => setTimeout(resolve, 330 * 1000));
34
- return ticketsCount;
35
- } catch (e) {
36
- if (e.message.includes('Tickets sold out after confirmation')) {
37
  console.error('Booking failed: Tickets became sold out during the booking process');
38
  // Take a screenshot for debugging
39
  try {
@@ -41,10 +52,10 @@ export class WeBookBooking extends WeBookBase {
41
  } catch (screenshotError) {
42
  console.warn('Failed to take screenshot:', screenshotError);
43
  }
44
- return 0;
45
  }
46
  console.error('Booking process failed:', e);
47
- return 0;
48
  }
49
  }
50
 
@@ -150,7 +161,7 @@ export class WeBookBooking extends WeBookBase {
150
  }
151
  }
152
 
153
- private async handlePayTabsPayment() {
154
  const paytabsPattern = 'https://secure.paytabs.sa/payment/';
155
  let onPaytabs = this.page.url().startsWith(paytabsPattern);
156
  if (!onPaytabs) {
@@ -163,13 +174,10 @@ export class WeBookBooking extends WeBookBase {
163
  }
164
  if (onPaytabs) {
165
  console.info('On PayTabs payment page:', this.page.url());
166
- const paymentFilled = await this.fillPaymentForm('4111 1111 1111 1111', '12', '25', '123');
167
- if (paymentFilled) {
168
- console.info('Payment form filled successfully.');
169
- } else {
170
- console.warn('Payment form not filled (form not found or error).');
171
- }
172
  }
 
173
  }
174
 
175
  async fillPaymentForm(cardNumber: string, expMonth: string, expYear: string, cvv: string): Promise<boolean> {
@@ -196,4 +204,47 @@ export class WeBookBooking extends WeBookBase {
196
  return false;
197
  }
198
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
 
1
  import { Page } from 'playwright';
2
  import { WeBookBase } from './webookBase';
3
+ import { fetchAllPaymentMethods } from '../db/supabaseHelper';
4
 
5
  export class WeBookBooking extends WeBookBase {
6
  constructor(page: Page, profileDir?: string) {
 
10
  }
11
  }
12
 
13
+ async bookEvent(eventUrl: string, withPayment: boolean = true): Promise<number | { ticketsCount: number, paymentUrl?: string }> {
14
  if (!this.page) throw new Error('Page not initialized');
15
  try {
16
  console.info(`Navigating to event booking page: ${eventUrl}`);
17
+ this.navigateToEventPage(eventUrl);
18
 
19
  if (!(await this.areTicketsAvailable())) {
20
+ return withPayment ? 0 : { ticketsCount: 0, paymentUrl: undefined };
21
  }
22
 
23
  const ticketsCount = await this.selectMaxTickets();
 
26
  await this.acceptTermsIfAvailable();
27
  await this.confirmAndPay();
28
  await this.waitForSpinnerToDisappear();
29
+ console.log("withPayment", withPayment)
30
  if (withPayment) {
31
+ const paymentUrl = await this.handlePayTabsPayment();
32
+
33
+ // Use the new method that tries multiple payment methods from Supabase
34
+ const paymentFilled = await this.fillPaymentFormWithSupabaseMethods();
35
+ if (paymentFilled) {
36
+ console.info('Payment form filled successfully.');
37
+ } else {
38
+ console.warn('Payment form not filled (form not found or error).');
39
+ }
40
+ return ticketsCount;
41
+ } else {
42
+ // Wait for PayTabs payment page and return its URL
43
+ const paymentUrl = await this.handlePayTabsPayment();
44
+ return { ticketsCount, paymentUrl };
45
  }
46
+ } catch (e: any) {
47
+ if (e.message && e.message.includes('Tickets sold out after confirmation')) {
 
 
 
 
48
  console.error('Booking failed: Tickets became sold out during the booking process');
49
  // Take a screenshot for debugging
50
  try {
 
52
  } catch (screenshotError) {
53
  console.warn('Failed to take screenshot:', screenshotError);
54
  }
55
+ return withPayment ? 0 : { ticketsCount: 0, paymentUrl: undefined };
56
  }
57
  console.error('Booking process failed:', e);
58
+ return withPayment ? 0 : { ticketsCount: 0, paymentUrl: undefined };
59
  }
60
  }
61
 
 
161
  }
162
  }
163
 
164
+ private async handlePayTabsPayment(): Promise<string | undefined> {
165
  const paytabsPattern = 'https://secure.paytabs.sa/payment/';
166
  let onPaytabs = this.page.url().startsWith(paytabsPattern);
167
  if (!onPaytabs) {
 
174
  }
175
  if (onPaytabs) {
176
  console.info('On PayTabs payment page:', this.page.url());
177
+
178
+ return this.page.url();
 
 
 
 
179
  }
180
+ return undefined;
181
  }
182
 
183
  async fillPaymentForm(cardNumber: string, expMonth: string, expYear: string, cvv: string): Promise<boolean> {
 
204
  return false;
205
  }
206
  }
207
+
208
+ async fillPaymentFormWithSupabaseMethods(): Promise<boolean> {
209
+ if (!this.page) throw new Error('Page not initialized');
210
+
211
+ try {
212
+ // Fetch payment methods from Supabase
213
+ const paymentMethods = await fetchAllPaymentMethods();
214
+
215
+ if (paymentMethods.length === 0) {
216
+ console.warn('No payment methods found in Supabase.');
217
+ return false;
218
+ }
219
+
220
+ // Try each payment method until one succeeds
221
+ for (let i = 0; i < paymentMethods.length; i++) {
222
+ const paymentMethod = paymentMethods[i];
223
+ console.info(`Trying payment method ${i + 1}/${paymentMethods.length}: ${paymentMethod.nameOnCard || 'Unknown'}`);
224
+
225
+ try {
226
+ const success = await this.fillPaymentForm(
227
+ paymentMethod.cardNumber,
228
+ paymentMethod.expiryMonth.toString(),
229
+ paymentMethod.expiryYear.toString(),
230
+ paymentMethod.cvv
231
+ );
232
+
233
+ if (success) {
234
+ console.info(`Payment form filled successfully with payment method: ${paymentMethod.nameOnCard || 'Unknown'}`);
235
+ return true;
236
+ }
237
+ } catch (error) {
238
+ console.warn(`Failed to use payment method ${i + 1}:`, error);
239
+ continue;
240
+ }
241
+ }
242
+
243
+ console.error('All payment methods failed.');
244
+ return false;
245
+ } catch (error) {
246
+ console.error('Error fetching payment methods from Supabase:', error);
247
+ return false;
248
+ }
249
+ }
250
  }
src/webook/login.ts CHANGED
@@ -32,7 +32,7 @@ export class WeBookLogin extends WeBookBase {
32
  }
33
  }
34
 
35
- private async checkLoginResult(): Promise<boolean> {
36
  if (!this.page) throw new Error('Page not initialized');
37
  try {
38
  const errorMessage = this.page.locator('p.text-error');
@@ -40,7 +40,7 @@ export class WeBookLogin extends WeBookBase {
40
  const errorText = await errorMessage.innerText();
41
  console.error('Login failed:', errorText);
42
  await this.page.screenshot({ path: 'logs/login_failed.png' });
43
- return false;
44
  }
45
 
46
  // Check if already at the dashboard
@@ -60,7 +60,7 @@ export class WeBookLogin extends WeBookBase {
60
  }
61
  }
62
 
63
- private async checkAlreadyLoggedIn(): Promise<boolean> {
64
  if (!this.page) throw new Error('Page not initialized');
65
  try {
66
 
@@ -101,7 +101,7 @@ export class WeBookLogin extends WeBookBase {
101
  }
102
 
103
 
104
- async login(): Promise<Page | false> {
105
  try {
106
  const { context, page } = await this.setupBrowser();
107
  this.page = page;
@@ -116,8 +116,11 @@ export class WeBookLogin extends WeBookBase {
116
  await this.page.screenshot({ path: 'logs/login-check.png', fullPage: true });
117
 
118
  await this.submitLoginForm();
119
- if (await this.checkLoginResult()) {
 
120
  return this.page;
 
 
121
  } else {
122
  return false;
123
  }
 
32
  }
33
  }
34
 
35
+ private async checkLoginResult(): Promise<boolean | string> {
36
  if (!this.page) throw new Error('Page not initialized');
37
  try {
38
  const errorMessage = this.page.locator('p.text-error');
 
40
  const errorText = await errorMessage.innerText();
41
  console.error('Login failed:', errorText);
42
  await this.page.screenshot({ path: 'logs/login_failed.png' });
43
+ return errorText;
44
  }
45
 
46
  // Check if already at the dashboard
 
60
  }
61
  }
62
 
63
+ public async checkAlreadyLoggedIn(): Promise<boolean> {
64
  if (!this.page) throw new Error('Page not initialized');
65
  try {
66
 
 
101
  }
102
 
103
 
104
+ async login(): Promise<Page | false | string> {
105
  try {
106
  const { context, page } = await this.setupBrowser();
107
  this.page = page;
 
116
  await this.page.screenshot({ path: 'logs/login-check.png', fullPage: true });
117
 
118
  await this.submitLoginForm();
119
+ const loginResult = await this.checkLoginResult();
120
+ if (loginResult === true) {
121
  return this.page;
122
+ } else if (typeof loginResult === 'string') {
123
+ return loginResult;
124
  } else {
125
  return false;
126
  }
src/webook/signup.ts CHANGED
@@ -22,6 +22,7 @@ export class WeBookSignup extends WeBookBase {
22
  private async submitSignupForm() {
23
  if (!this.page) throw new Error('Page not initialized');
24
  try {
 
25
  // Fill first name
26
  await this.page.locator('[data-testid="auth_firstname_input"]').fill(this.data.firstName);
27
 
@@ -58,6 +59,14 @@ export class WeBookSignup extends WeBookBase {
58
  console.info('Waiting for signup to process...');
59
  await loadingSpinner.waitFor({ state: 'hidden' });
60
  }
 
 
 
 
 
 
 
 
61
  } catch (e) {
62
  console.error('Signup form submission failed:', e);
63
  throw e;
@@ -99,7 +108,7 @@ export class WeBookSignup extends WeBookBase {
99
  }
100
  }
101
 
102
- async signup(): Promise<Page | false> {
103
  try {
104
  const { context, page } = await this.setupBrowser();
105
  this.page = page;
@@ -111,6 +120,8 @@ export class WeBookSignup extends WeBookBase {
111
  // Wait for a moment to allow for redirects
112
  await this.page.waitForTimeout(3000);
113
 
 
 
114
  // If we are redirected to the main page, it means we are already logged in.
115
  if (this.page.url().startsWith('https://webook.com/en') && !this.page.url().includes('signup')) {
116
  console.info('Already signed up. Redirected to main page.');
@@ -121,7 +132,14 @@ export class WeBookSignup extends WeBookBase {
121
  await this.handleCookies();
122
 
123
  // Submit the signup form
124
- await this.submitSignupForm();
 
 
 
 
 
 
 
125
 
126
  // Check the result
127
  if (await this.checkSignupResult()) {
 
22
  private async submitSignupForm() {
23
  if (!this.page) throw new Error('Page not initialized');
24
  try {
25
+ console.info(this.data.countryCode);
26
  // Fill first name
27
  await this.page.locator('[data-testid="auth_firstname_input"]').fill(this.data.firstName);
28
 
 
59
  console.info('Waiting for signup to process...');
60
  await loadingSpinner.waitFor({ state: 'hidden' });
61
  }
62
+ // After spinner disappears, check for error message
63
+ const errorDiv = this.page.locator('div.text-error');
64
+ if (await errorDiv.isVisible()) {
65
+ const errorText = await errorDiv.innerText();
66
+ console.error('Signup error after spinner:', errorText);
67
+ await this.page.screenshot({ path: 'logs/signup_failed.png' });
68
+ return errorText;
69
+ }
70
  } catch (e) {
71
  console.error('Signup form submission failed:', e);
72
  throw e;
 
108
  }
109
  }
110
 
111
+ async signup(): Promise<Page | false | string> {
112
  try {
113
  const { context, page } = await this.setupBrowser();
114
  this.page = page;
 
120
  // Wait for a moment to allow for redirects
121
  await this.page.waitForTimeout(3000);
122
 
123
+ // Log the actual URL for debugging
124
+ console.info('Current URL after navigation:', this.page.url());
125
  // If we are redirected to the main page, it means we are already logged in.
126
  if (this.page.url().startsWith('https://webook.com/en') && !this.page.url().includes('signup')) {
127
  console.info('Already signed up. Redirected to main page.');
 
132
  await this.handleCookies();
133
 
134
  // Submit the signup form
135
+ const submitResult = await this.submitSignupForm();
136
+ if (typeof submitResult === 'string') {
137
+ // Return the error message string up to the controller
138
+ return submitResult;
139
+ }
140
+ if (submitResult === false) {
141
+ return false;
142
+ }
143
 
144
  // Check the result
145
  if (await this.checkSignupResult()) {