Spaces:
Paused
Paused
Mohammed Foud
commited on
Commit
·
ca368c8
1
Parent(s):
f12461b
all
Browse files- corrected_sql_statements.sql +245 -0
- src/bots/botManager.ts +4 -4
- src/bots/handlers/webookHandlers.ts +287 -66
- src/bots/utils/handlerUtils.ts +6 -1
- src/bots/utils/keyboardUtils.ts +9 -8
- src/bots/utils/messageManager.ts +12 -1
- src/controllers.ts +116 -17
- src/db/supabaseHelper.ts +22 -0
- src/routes.ts +3 -1
- src/webook/book.ts +70 -19
- src/webook/login.ts +8 -5
- src/webook/signup.ts +20 -2
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, {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
|
|
|
|
|
|
| 193 |
bookByUrlStates.set(telegramId, { step: 1 });
|
| 194 |
console.log('[BookByUrl] Set state for', telegramId, { step: 1 });
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 399 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
return true;
|
| 401 |
}
|
| 402 |
|
| 403 |
-
let infoMsg =
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
|
|
|
|
|
|
| 408 |
// Ask user if they want to make payment automatically
|
| 409 |
-
await ctx.reply(
|
| 410 |
-
infoMsg
|
| 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('
|
| 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('
|
| 451 |
} else {
|
| 452 |
-
await ctx.reply('
|
| 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('
|
| 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('
|
| 470 |
bookByUrlStates.delete(telegramId);
|
| 471 |
return;
|
| 472 |
}
|
| 473 |
|
| 474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
|
| 476 |
-
for (const
|
| 477 |
if (ticketsObtained >= ticketsNeeded) {
|
| 478 |
-
await ctx.reply('
|
| 479 |
break;
|
| 480 |
}
|
| 481 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
let login: WeBookLogin | undefined;
|
| 483 |
try {
|
| 484 |
-
|
| 485 |
login = new WeBookLogin(account.email, account.password, `${account.id}`);
|
| 486 |
const page = await login.login();
|
| 487 |
|
| 488 |
-
if (!page) {
|
| 489 |
-
|
| 490 |
-
|
| 491 |
}
|
| 492 |
-
|
|
|
|
| 493 |
const booking = new WeBookBooking(page);
|
| 494 |
-
const
|
| 495 |
-
|
| 496 |
-
if (
|
| 497 |
-
|
| 498 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
} else {
|
| 500 |
-
|
| 501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
} catch (e: any) {
|
| 503 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
} finally {
|
| 505 |
if (login) {
|
| 506 |
await login.close();
|
| 507 |
}
|
| 508 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
}
|
| 510 |
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 514 |
}
|
| 515 |
-
|
| 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 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
|
|
|
| 549 |
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 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 |
-
|
| 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 |
-
|
| 36 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 461 |
|
| 462 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 474 |
|
| 475 |
// Update account status in database
|
| 476 |
await updateDataInTable('account_webhook', {
|
| 477 |
id,
|
| 478 |
-
|
|
|
|
| 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:
|
| 487 |
-
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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 167 |
-
|
| 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
|
| 44 |
}
|
| 45 |
|
| 46 |
// Check if already at the dashboard
|
|
@@ -60,7 +60,7 @@ export class WeBookLogin extends WeBookBase {
|
|
| 60 |
}
|
| 61 |
}
|
| 62 |
|
| 63 |
-
|
| 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 |
-
|
|
|
|
| 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()) {
|