Spaces:
Paused
Paused
Mohammed Foud commited on
Commit ·
1029f50
1
Parent(s): 5c9af5b
all
Browse files- WEBHOOK_EVENTS_FEATURE.md +156 -0
- db/full_backup.sql +0 -0
- db/get.sh +1 -0
- db/push.sh +1 -0
- src/bots/handlers/index.ts +2 -0
- src/bots/handlers/webookHandlers.ts +336 -0
- src/bots/utils/keyboardUtils.ts +1 -0
- src/bots/utils/messageManager.ts +20 -0
- src/index.ts +1 -1
- src/webook/main.ts +8 -8
- src/webook/webookApi.ts +350 -0
- src/webook/webookBase.ts +1 -1
WEBHOOK_EVENTS_FEATURE.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# WeBook Events Feature
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
The WeBook Events feature allows users to browse and search for events from the WeBook platform directly through the Telegram bot. Users can filter events by date, price, and other criteria.
|
| 5 |
+
|
| 6 |
+
## Features
|
| 7 |
+
|
| 8 |
+
### 🎭 Event Browsing
|
| 9 |
+
- Browse events from WeBook platform
|
| 10 |
+
- View event details including title, date, location, price, and venue
|
| 11 |
+
- Paginated results (5 events per page)
|
| 12 |
+
|
| 13 |
+
### 🔍 Filtering Options
|
| 14 |
+
- **Date Filters:**
|
| 15 |
+
- Today's events
|
| 16 |
+
- Tomorrow's events
|
| 17 |
+
- This week's events
|
| 18 |
+
|
| 19 |
+
- **Price Filters:**
|
| 20 |
+
- Free events only
|
| 21 |
+
- Paid events only
|
| 22 |
+
|
| 23 |
+
- **Clear Filters:** Reset all applied filters
|
| 24 |
+
|
| 25 |
+
### 📱 User Interface
|
| 26 |
+
- Inline keyboard navigation
|
| 27 |
+
- Pagination controls (Previous/Next)
|
| 28 |
+
- Active filters display
|
| 29 |
+
- Back to main menu option
|
| 30 |
+
|
| 31 |
+
## Technical Implementation
|
| 32 |
+
|
| 33 |
+
### Files Modified/Created
|
| 34 |
+
|
| 35 |
+
1. **`src/bots/handlers/webookHandlers.ts`** - Main handler for WeBook events
|
| 36 |
+
2. **`src/bots/utils/keyboardUtils.ts`** - Updated to include WeBook Events button
|
| 37 |
+
3. **`src/bots/handlers/index.ts`** - Added WeBook handlers setup
|
| 38 |
+
4. **`src/bots/utils/messageManager.ts`** - Added error messages
|
| 39 |
+
|
| 40 |
+
### Key Components
|
| 41 |
+
|
| 42 |
+
#### WeBook API Integration
|
| 43 |
+
- Uses the existing `WeBookAPI` class from `src/webook/webookApi.ts`
|
| 44 |
+
- Fetches events with filtering and pagination
|
| 45 |
+
- Handles API errors gracefully
|
| 46 |
+
|
| 47 |
+
#### User State Management
|
| 48 |
+
- Stores user pagination state and filters
|
| 49 |
+
- Maintains state per user (telegram ID)
|
| 50 |
+
- Resets to first page when applying new filters
|
| 51 |
+
|
| 52 |
+
#### Event Display Format
|
| 53 |
+
```
|
| 54 |
+
🎭 WeBook Events
|
| 55 |
+
|
| 56 |
+
📊 Showing 1-5 of 25 events
|
| 57 |
+
|
| 58 |
+
🔍 Active Filters: Today, Free Events
|
| 59 |
+
|
| 60 |
+
1. Event Title
|
| 61 |
+
Event Subtitle
|
| 62 |
+
📅 Mon, Jan 15, 2024
|
| 63 |
+
📍 Riyadh
|
| 64 |
+
💰 Free
|
| 65 |
+
🎪 Venue Name
|
| 66 |
+
|
| 67 |
+
2. Another Event
|
| 68 |
+
...
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
### Keyboard Layout
|
| 72 |
+
|
| 73 |
+
#### Main Menu (Logged In)
|
| 74 |
+
```
|
| 75 |
+
🔍 Browse Services
|
| 76 |
+
🎭 WeBook Events
|
| 77 |
+
[Profile] [Change Language]
|
| 78 |
+
[Top Up Balance] [History]
|
| 79 |
+
[Back]
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
#### Events Browser
|
| 83 |
+
```
|
| 84 |
+
[📅 Today] [📅 Tomorrow] [📅 This Week]
|
| 85 |
+
[🆓 Free] [💰 Paid] [🧹 Clear Filters]
|
| 86 |
+
[⬅️ Previous] [📄 1/5] [Next ➡️]
|
| 87 |
+
[🔙 Back to Menu]
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
## Usage
|
| 91 |
+
|
| 92 |
+
1. **Access Events:** Click "🎭 WeBook Events" from the main menu
|
| 93 |
+
2. **Browse Events:** View events with pagination
|
| 94 |
+
3. **Apply Filters:** Use filter buttons to narrow results
|
| 95 |
+
4. **Clear Filters:** Reset to show all events
|
| 96 |
+
5. **Navigate:** Use Previous/Next buttons to browse pages
|
| 97 |
+
|
| 98 |
+
## Error Handling
|
| 99 |
+
|
| 100 |
+
- **API Errors:** Graceful fallback with user-friendly messages
|
| 101 |
+
- **Invalid States:** Automatic state reset and recovery
|
| 102 |
+
- **Network Issues:** Retry mechanism and error notifications
|
| 103 |
+
|
| 104 |
+
## Configuration
|
| 105 |
+
|
| 106 |
+
### Default Settings
|
| 107 |
+
- **Country Code:** SA (Saudi Arabia)
|
| 108 |
+
- **Events Per Page:** 5
|
| 109 |
+
- **Default Language:** Arabic (ar-SA)
|
| 110 |
+
|
| 111 |
+
### Customization
|
| 112 |
+
- Modify `EVENTS_PER_PAGE` constant in `webookHandlers.ts`
|
| 113 |
+
- Update default filters in `handleWeBookEventsAction`
|
| 114 |
+
- Add new filter types in `handleWeBookFilterAction`
|
| 115 |
+
|
| 116 |
+
## Future Enhancements
|
| 117 |
+
|
| 118 |
+
- [ ] Category-based filtering
|
| 119 |
+
- [ ] Location-based filtering
|
| 120 |
+
- [ ] Event booking integration
|
| 121 |
+
- [ ] Event reminders
|
| 122 |
+
- [ ] Favorite events
|
| 123 |
+
- [ ] Event sharing
|
| 124 |
+
- [ ] Advanced search with text input
|
| 125 |
+
|
| 126 |
+
## Dependencies
|
| 127 |
+
|
| 128 |
+
- `telegraf` - Telegram bot framework
|
| 129 |
+
- `axios` - HTTP client for API calls
|
| 130 |
+
- `src/webook/webookApi.ts` - WeBook API integration
|
| 131 |
+
|
| 132 |
+
## Testing
|
| 133 |
+
|
| 134 |
+
To test the feature:
|
| 135 |
+
|
| 136 |
+
1. Start the bot
|
| 137 |
+
2. Login to the bot
|
| 138 |
+
3. Click "🎭 WeBook Events" from the main menu
|
| 139 |
+
4. Test pagination and filtering
|
| 140 |
+
5. Verify error handling with network issues
|
| 141 |
+
|
| 142 |
+
## Troubleshooting
|
| 143 |
+
|
| 144 |
+
### Common Issues
|
| 145 |
+
|
| 146 |
+
1. **No events showing:** Check WeBook API connectivity
|
| 147 |
+
2. **Filters not working:** Verify filter logic in handlers
|
| 148 |
+
3. **Pagination issues:** Check user state management
|
| 149 |
+
4. **Keyboard not responding:** Verify action handlers setup
|
| 150 |
+
|
| 151 |
+
### Debug Steps
|
| 152 |
+
|
| 153 |
+
1. Check bot logs for errors
|
| 154 |
+
2. Verify WeBook API responses
|
| 155 |
+
3. Test individual handler functions
|
| 156 |
+
4. Check user state persistence
|
db/full_backup.sql
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
db/get.sh
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pg_dump "postgresql://postgres.bxfyodnvfroddubzxgyj:009988Ppooii@aws-0-us-west-1.pooler.supabase.com:5432/postgres" --no-owner --format=plain --file=full_backup.sql
|
db/push.sh
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
psql "postgresql://postgres.aoptpqvwquunkmpihygt:009988Ppooii@aws-0-eu-north-1.pooler.supabase.com:5432/postgres" < full_backup.sql
|
src/bots/handlers/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { setupHistoryHandlers } from './historyHandlers';
|
|
| 5 |
import { setupBalanceHandlers } from './balanceHandlers';
|
| 6 |
import { setupLanguageHandlers } from './languageHandlers';
|
| 7 |
import { setupProfileHandlers } from './profileHandlers';
|
|
|
|
| 8 |
import { handleGiftAmountInput, handleGiftEmailInput, giftStates, handleGiftBalanceAction } from './giftHandlers';
|
| 9 |
import { BotContext } from '../types/botTypes';
|
| 10 |
import type { ParseMode } from 'telegraf/types';
|
|
@@ -17,6 +18,7 @@ export const setupCallbackHandlers = (bot: any) => {
|
|
| 17 |
setupBalanceHandlers(bot);
|
| 18 |
setupLanguageHandlers(bot);
|
| 19 |
setupProfileHandlers(bot);
|
|
|
|
| 20 |
|
| 21 |
bot.action('gift_balance', async (ctx: BotContext) => {
|
| 22 |
const result = await handleGiftBalanceAction(ctx);
|
|
|
|
| 5 |
import { setupBalanceHandlers } from './balanceHandlers';
|
| 6 |
import { setupLanguageHandlers } from './languageHandlers';
|
| 7 |
import { setupProfileHandlers } from './profileHandlers';
|
| 8 |
+
import { setupWeBookHandlers } from './webookHandlers';
|
| 9 |
import { handleGiftAmountInput, handleGiftEmailInput, giftStates, handleGiftBalanceAction } from './giftHandlers';
|
| 10 |
import { BotContext } from '../types/botTypes';
|
| 11 |
import type { ParseMode } from 'telegraf/types';
|
|
|
|
| 18 |
setupBalanceHandlers(bot);
|
| 19 |
setupLanguageHandlers(bot);
|
| 20 |
setupProfileHandlers(bot);
|
| 21 |
+
setupWeBookHandlers(bot);
|
| 22 |
|
| 23 |
bot.action('gift_balance', async (ctx: BotContext) => {
|
| 24 |
const result = await handleGiftBalanceAction(ctx);
|
src/bots/handlers/webookHandlers.ts
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { BotContext } from "../types/botTypes";
|
| 2 |
+
import { createLogger } from '../../utils/logger';
|
| 3 |
+
import { callbackReplyHandler } from "../utils/handlerUtils";
|
| 4 |
+
import { messageManager } from "../utils/messageManager";
|
| 5 |
+
import { webookAPI, EventFilters } from '../../webook/webookApi';
|
| 6 |
+
import { Markup } from "telegraf";
|
| 7 |
+
import { getBackToMainMenuButton, getLoggedInMenuKeyboard } from "../utils/keyboardUtils";
|
| 8 |
+
|
| 9 |
+
const logger = createLogger('WeBookHandlers');
|
| 10 |
+
|
| 11 |
+
// Store user states for pagination and filters
|
| 12 |
+
const userStates = new Map<number, {
|
| 13 |
+
page: number;
|
| 14 |
+
filters: EventFilters;
|
| 15 |
+
totalEvents: number;
|
| 16 |
+
}>();
|
| 17 |
+
|
| 18 |
+
const EVENTS_PER_PAGE = 5;
|
| 19 |
+
|
| 20 |
+
export const setupWeBookHandlers = (bot: any) => {
|
| 21 |
+
bot.action('webook_events', callbackReplyHandler(handleWeBookEventsAction));
|
| 22 |
+
bot.action(/^webook_page_(\d+)$/, callbackReplyHandler(handleWeBookPageAction));
|
| 23 |
+
bot.action(/^webook_filter_(.+)$/, callbackReplyHandler(handleWeBookFilterAction));
|
| 24 |
+
bot.action('webook_back_to_menu', callbackReplyHandler(handleWeBookBackToMenuAction));
|
| 25 |
+
bot.action('webook_clear_filters', callbackReplyHandler(handleWeBookClearFiltersAction));
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
export const handleWeBookEventsAction = async (ctx: BotContext) => {
|
| 29 |
+
try {
|
| 30 |
+
const telegramId = ctx.from?.id;
|
| 31 |
+
if (!telegramId) {
|
| 32 |
+
return {
|
| 33 |
+
message: messageManager.getMessage('error_user_not_found'),
|
| 34 |
+
options: getBackToMainMenuButton()
|
| 35 |
+
};
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
// Initialize user state
|
| 39 |
+
userStates.set(telegramId, {
|
| 40 |
+
page: 0,
|
| 41 |
+
filters: { country_code: 'SA', limit: EVENTS_PER_PAGE },
|
| 42 |
+
totalEvents: 0
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
+
return await fetchAndDisplayEvents(telegramId, 0);
|
| 46 |
+
} catch (error: any) {
|
| 47 |
+
logger.error(`Error in WeBook events action: ${error.message}`);
|
| 48 |
+
return {
|
| 49 |
+
message: messageManager.getMessage('error_general'),
|
| 50 |
+
options: getBackToMainMenuButton()
|
| 51 |
+
};
|
| 52 |
+
}
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
export const handleWeBookPageAction = async (ctx: BotContext) => {
|
| 56 |
+
try {
|
| 57 |
+
const telegramId = ctx.from?.id;
|
| 58 |
+
if (!telegramId) {
|
| 59 |
+
return {
|
| 60 |
+
message: messageManager.getMessage('error_user_not_found'),
|
| 61 |
+
options: getBackToMainMenuButton()
|
| 62 |
+
};
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
const match = ctx.match;
|
| 66 |
+
if (!match) {
|
| 67 |
+
return {
|
| 68 |
+
message: messageManager.getMessage('error_invalid_page'),
|
| 69 |
+
options: getBackToMainMenuButton()
|
| 70 |
+
};
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
const page = parseInt(match[1]);
|
| 74 |
+
return await fetchAndDisplayEvents(telegramId, page);
|
| 75 |
+
} catch (error: any) {
|
| 76 |
+
logger.error(`Error in WeBook page action: ${error.message}`);
|
| 77 |
+
return {
|
| 78 |
+
message: messageManager.getMessage('error_general'),
|
| 79 |
+
options: getBackToMainMenuButton()
|
| 80 |
+
};
|
| 81 |
+
}
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
export const handleWeBookFilterAction = async (ctx: BotContext) => {
|
| 85 |
+
try {
|
| 86 |
+
const telegramId = ctx.from?.id;
|
| 87 |
+
if (!telegramId) {
|
| 88 |
+
return {
|
| 89 |
+
message: messageManager.getMessage('error_user_not_found'),
|
| 90 |
+
options: getBackToMainMenuButton()
|
| 91 |
+
};
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
const match = ctx.match;
|
| 95 |
+
if (!match) {
|
| 96 |
+
return {
|
| 97 |
+
message: messageManager.getMessage('error_invalid_filter'),
|
| 98 |
+
options: getBackToMainMenuButton()
|
| 99 |
+
};
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
const filterType = match[1];
|
| 103 |
+
const userState = userStates.get(telegramId);
|
| 104 |
+
if (!userState) {
|
| 105 |
+
return {
|
| 106 |
+
message: messageManager.getMessage('error_user_state_not_found'),
|
| 107 |
+
options: getBackToMainMenuButton()
|
| 108 |
+
};
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Apply filter based on type
|
| 112 |
+
switch (filterType) {
|
| 113 |
+
case 'today':
|
| 114 |
+
userState.filters.date_filter = 'today';
|
| 115 |
+
break;
|
| 116 |
+
case 'tomorrow':
|
| 117 |
+
userState.filters.date_filter = 'tomorrow';
|
| 118 |
+
break;
|
| 119 |
+
case 'this_week':
|
| 120 |
+
userState.filters.date_filter = 'this_week';
|
| 121 |
+
break;
|
| 122 |
+
case 'free':
|
| 123 |
+
userState.filters.price_max = 0;
|
| 124 |
+
break;
|
| 125 |
+
case 'paid':
|
| 126 |
+
userState.filters.price_min = 1;
|
| 127 |
+
break;
|
| 128 |
+
default:
|
| 129 |
+
// Handle category filters
|
| 130 |
+
if (filterType.startsWith('cat_')) {
|
| 131 |
+
const category = filterType.replace('cat_', '');
|
| 132 |
+
userState.filters.category = category;
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
userState.page = 0; // Reset to first page when applying filter
|
| 137 |
+
userStates.set(telegramId, userState);
|
| 138 |
+
|
| 139 |
+
return await fetchAndDisplayEvents(telegramId, 0);
|
| 140 |
+
} catch (error: any) {
|
| 141 |
+
logger.error(`Error in WeBook filter action: ${error.message}`);
|
| 142 |
+
return {
|
| 143 |
+
message: messageManager.getMessage('error_general'),
|
| 144 |
+
options: getBackToMainMenuButton()
|
| 145 |
+
};
|
| 146 |
+
}
|
| 147 |
+
};
|
| 148 |
+
|
| 149 |
+
export const handleWeBookClearFiltersAction = async (ctx: BotContext) => {
|
| 150 |
+
try {
|
| 151 |
+
const telegramId = ctx.from?.id;
|
| 152 |
+
if (!telegramId) {
|
| 153 |
+
return {
|
| 154 |
+
message: messageManager.getMessage('error_user_not_found'),
|
| 155 |
+
options: getBackToMainMenuButton()
|
| 156 |
+
};
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
// Reset user state to default
|
| 160 |
+
userStates.set(telegramId, {
|
| 161 |
+
page: 0,
|
| 162 |
+
filters: { country_code: 'SA', limit: EVENTS_PER_PAGE },
|
| 163 |
+
totalEvents: 0
|
| 164 |
+
});
|
| 165 |
+
|
| 166 |
+
return await fetchAndDisplayEvents(telegramId, 0);
|
| 167 |
+
} catch (error: any) {
|
| 168 |
+
logger.error(`Error in WeBook clear filters action: ${error.message}`);
|
| 169 |
+
return {
|
| 170 |
+
message: messageManager.getMessage('error_general'),
|
| 171 |
+
options: getBackToMainMenuButton()
|
| 172 |
+
};
|
| 173 |
+
}
|
| 174 |
+
};
|
| 175 |
+
|
| 176 |
+
export const handleWeBookBackToMenuAction = async (ctx: BotContext) => {
|
| 177 |
+
return {
|
| 178 |
+
message: messageManager.getMessage('main_menu_welcome_back').replace('{name}', ctx.from?.first_name || 'User'),
|
| 179 |
+
options: getLoggedInMenuKeyboard()
|
| 180 |
+
};
|
| 181 |
+
};
|
| 182 |
+
|
| 183 |
+
async function fetchAndDisplayEvents(telegramId: number, page: number) {
|
| 184 |
+
try {
|
| 185 |
+
const userState = userStates.get(telegramId);
|
| 186 |
+
if (!userState) {
|
| 187 |
+
return {
|
| 188 |
+
message: messageManager.getMessage('error_user_state_not_found'),
|
| 189 |
+
options: getBackToMainMenuButton()
|
| 190 |
+
};
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
// Update page and calculate skip
|
| 194 |
+
userState.page = page;
|
| 195 |
+
const skip = page * EVENTS_PER_PAGE;
|
| 196 |
+
const filters = { ...userState.filters, skip, limit: EVENTS_PER_PAGE };
|
| 197 |
+
|
| 198 |
+
// Fetch events from WeBook API
|
| 199 |
+
const response = await webookAPI.getEvents(filters);
|
| 200 |
+
const events = response.data.eventCollection.items;
|
| 201 |
+
const total = response.data.eventCollection.total;
|
| 202 |
+
|
| 203 |
+
userState.totalEvents = total;
|
| 204 |
+
userStates.set(telegramId, userState);
|
| 205 |
+
|
| 206 |
+
if (events.length === 0) {
|
| 207 |
+
return {
|
| 208 |
+
message: '🎭 No events found with the current filters.\n\nTry adjusting your search criteria or clearing filters.',
|
| 209 |
+
options: getWeBookEventsKeyboard(page, total, userState.filters)
|
| 210 |
+
};
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// Build events message
|
| 214 |
+
let message = `🎭 <b>WeBook Events</b>\n\n`;
|
| 215 |
+
message += `📊 Showing ${skip + 1}-${Math.min(skip + events.length, total)} of ${total} events\n\n`;
|
| 216 |
+
|
| 217 |
+
// Add active filters info
|
| 218 |
+
const activeFilters = getActiveFiltersText(userState.filters);
|
| 219 |
+
if (activeFilters) {
|
| 220 |
+
message += `🔍 <b>Active Filters:</b> ${activeFilters}\n\n`;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
// Display events
|
| 224 |
+
events.forEach((event, index) => {
|
| 225 |
+
const eventNumber = skip + index + 1;
|
| 226 |
+
message += `${eventNumber}. <b>${event.title || 'Untitled Event'}</b>\n`;
|
| 227 |
+
|
| 228 |
+
if (event.subtitle) {
|
| 229 |
+
message += ` ${event.subtitle}\n`;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
if (event.schedule?.openDateTime) {
|
| 233 |
+
const eventDate = new Date(event.schedule.openDateTime).toLocaleDateString('en-US', {
|
| 234 |
+
weekday: 'short',
|
| 235 |
+
year: 'numeric',
|
| 236 |
+
month: 'short',
|
| 237 |
+
day: 'numeric'
|
| 238 |
+
});
|
| 239 |
+
message += ` 📅 ${eventDate}\n`;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
if (event.location?.city) {
|
| 243 |
+
message += ` 📍 ${event.location.city}\n`;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
if (event.startingPrice !== undefined) {
|
| 247 |
+
const priceText = event.startingPrice === 0 ? 'Free' : `${event.startingPrice} ${event.currencyCode || 'SAR'}`;
|
| 248 |
+
message += ` 💰 ${priceText}\n`;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
if (event.zone?.title) {
|
| 252 |
+
message += ` 🎪 ${event.zone.title}\n`;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
message += '\n';
|
| 256 |
+
});
|
| 257 |
+
|
| 258 |
+
return {
|
| 259 |
+
message,
|
| 260 |
+
options: getWeBookEventsKeyboard(page, total, userState.filters)
|
| 261 |
+
};
|
| 262 |
+
} catch (error: any) {
|
| 263 |
+
logger.error(`Error fetching WeBook events: ${error.message}`);
|
| 264 |
+
return {
|
| 265 |
+
message: '❌ Error fetching events. Please try again later.',
|
| 266 |
+
options: getBackToMainMenuButton()
|
| 267 |
+
};
|
| 268 |
+
}
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
function getWeBookEventsKeyboard(page: number, total: number, filters: EventFilters) {
|
| 272 |
+
const buttons = [];
|
| 273 |
+
const totalPages = Math.ceil(total / EVENTS_PER_PAGE);
|
| 274 |
+
|
| 275 |
+
// Filter buttons
|
| 276 |
+
const filterRow = [];
|
| 277 |
+
filterRow.push(Markup.button.callback('📅 Today', 'webook_filter_today'));
|
| 278 |
+
filterRow.push(Markup.button.callback('📅 Tomorrow', 'webook_filter_tomorrow'));
|
| 279 |
+
filterRow.push(Markup.button.callback('📅 This Week', 'webook_filter_this_week'));
|
| 280 |
+
buttons.push(filterRow);
|
| 281 |
+
|
| 282 |
+
const priceRow = [];
|
| 283 |
+
priceRow.push(Markup.button.callback('🆓 Free', 'webook_filter_free'));
|
| 284 |
+
priceRow.push(Markup.button.callback('💰 Paid', 'webook_filter_paid'));
|
| 285 |
+
priceRow.push(Markup.button.callback('🧹 Clear Filters', 'webook_clear_filters'));
|
| 286 |
+
buttons.push(priceRow);
|
| 287 |
+
|
| 288 |
+
// Pagination buttons
|
| 289 |
+
const paginationRow = [];
|
| 290 |
+
if (page > 0) {
|
| 291 |
+
paginationRow.push(Markup.button.callback('⬅️ Previous', `webook_page_${page - 1}`));
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
paginationRow.push(Markup.button.callback(`📄 ${page + 1}/${totalPages}`, 'noop'));
|
| 295 |
+
|
| 296 |
+
if (page < totalPages - 1) {
|
| 297 |
+
paginationRow.push(Markup.button.callback('Next ➡️', `webook_page_${page + 1}`));
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
if (paginationRow.length > 0) {
|
| 301 |
+
buttons.push(paginationRow);
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
// Back button
|
| 305 |
+
buttons.push([Markup.button.callback('🔙 Back to Menu', 'webook_back_to_menu')]);
|
| 306 |
+
|
| 307 |
+
return Markup.inlineKeyboard(buttons);
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
function getActiveFiltersText(filters: EventFilters): string {
|
| 311 |
+
const activeFilters = [];
|
| 312 |
+
|
| 313 |
+
if (filters.date_filter) {
|
| 314 |
+
const dateMap = {
|
| 315 |
+
'today': 'Today',
|
| 316 |
+
'tomorrow': 'Tomorrow',
|
| 317 |
+
'this_week': 'This Week'
|
| 318 |
+
};
|
| 319 |
+
activeFilters.push(dateMap[filters.date_filter]);
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
if (filters.price_min !== undefined && filters.price_min > 0) {
|
| 323 |
+
activeFilters.push('Paid Events');
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
if (filters.price_max !== undefined && filters.price_max === 0) {
|
| 327 |
+
activeFilters.push('Free Events');
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
if (filters.category) {
|
| 331 |
+
const category = Array.isArray(filters.category) ? filters.category[0] : filters.category;
|
| 332 |
+
activeFilters.push(`Category: ${category}`);
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
return activeFilters.length > 0 ? activeFilters.join(', ') : '';
|
| 336 |
+
}
|
src/bots/utils/keyboardUtils.ts
CHANGED
|
@@ -22,6 +22,7 @@ export const getMainMenuKeyboard = () => {
|
|
| 22 |
export const getLoggedInMenuKeyboard = () => {
|
| 23 |
return Markup.inlineKeyboard([
|
| 24 |
[Markup.button.callback('🔍 Browse Services', 'browse_services')],
|
|
|
|
| 25 |
[
|
| 26 |
Markup.button.callback(messageManager.getMessage('btn_profile'), 'profile'),
|
| 27 |
Markup.button.callback(messageManager.getMessage('btn_change_language'), 'change_language')
|
|
|
|
| 22 |
export const getLoggedInMenuKeyboard = () => {
|
| 23 |
return Markup.inlineKeyboard([
|
| 24 |
[Markup.button.callback('🔍 Browse Services', 'browse_services')],
|
| 25 |
+
[Markup.button.callback('🎭 WeBook Events', 'webook_events')],
|
| 26 |
[
|
| 27 |
Markup.button.callback(messageManager.getMessage('btn_profile'), 'profile'),
|
| 28 |
Markup.button.callback(messageManager.getMessage('btn_change_language'), 'change_language')
|
src/bots/utils/messageManager.ts
CHANGED
|
@@ -98,6 +98,26 @@ class MessageManager {
|
|
| 98 |
'no_affordable_products': {
|
| 99 |
ar_value: 'عذراً، لا توجد منتجات يمكنك شراؤها برصيدك الحالي ({balance}).',
|
| 100 |
en_value: 'Sorry, no products can be bought with your current balance ({balance}).'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
}
|
| 102 |
};
|
| 103 |
|
|
|
|
| 98 |
'no_affordable_products': {
|
| 99 |
ar_value: 'عذراً، لا توجد منتجات يمكنك شراؤها برصيدك الحالي ({balance}).',
|
| 100 |
en_value: 'Sorry, no products can be bought with your current balance ({balance}).'
|
| 101 |
+
},
|
| 102 |
+
'error_user_not_found': {
|
| 103 |
+
ar_value: '❌ لم يتم العثور على معلومات المستخدم.',
|
| 104 |
+
en_value: '❌ User information not found.'
|
| 105 |
+
},
|
| 106 |
+
'error_invalid_page': {
|
| 107 |
+
ar_value: '❌ رقم الصفحة غير صحيح.',
|
| 108 |
+
en_value: '❌ Invalid page number.'
|
| 109 |
+
},
|
| 110 |
+
'error_invalid_filter': {
|
| 111 |
+
ar_value: '❌ نوع الفلتر غير صحيح.',
|
| 112 |
+
en_value: '❌ Invalid filter type.'
|
| 113 |
+
},
|
| 114 |
+
'error_user_state_not_found': {
|
| 115 |
+
ar_value: '❌ لم يتم العثور على حالة المستخدم.',
|
| 116 |
+
en_value: '❌ User state not found.'
|
| 117 |
+
},
|
| 118 |
+
'error_general': {
|
| 119 |
+
ar_value: '❌ حدث خطأ. يرجى المحاولة مرة أخرى.',
|
| 120 |
+
en_value: '❌ An error occurred. Please try again.'
|
| 121 |
}
|
| 122 |
};
|
| 123 |
|
src/index.ts
CHANGED
|
@@ -37,4 +37,4 @@ startServer();
|
|
| 37 |
// To use the WeBook main function, uncomment the following line:
|
| 38 |
webookMain().catch(e => { console.error('WeBook Fatal error:', e); });
|
| 39 |
|
| 40 |
-
// handleAddTelegrafBot("
|
|
|
|
| 37 |
// To use the WeBook main function, uncomment the following line:
|
| 38 |
webookMain().catch(e => { console.error('WeBook Fatal error:', e); });
|
| 39 |
|
| 40 |
+
// handleAddTelegrafBot("1e186256-fdd6-453f-8344-fd824660b8bd")
|
src/webook/main.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { WeBookBooking } from './book';
|
|
| 4 |
export async function main() {
|
| 5 |
const email = 'mfoud444@gmail.com'; // Replace with your email
|
| 6 |
const password = '009988Ppooii@@@@'; // Replace with your password
|
| 7 |
-
const eventUrl = 'https://webook.com/en/
|
| 8 |
|
| 9 |
const login = new WeBookLogin(email, password);
|
| 10 |
const page = await login.login();
|
|
@@ -13,11 +13,11 @@ export async function main() {
|
|
| 13 |
process.exit(1);
|
| 14 |
}
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
}
|
|
|
|
| 4 |
export async function main() {
|
| 5 |
const email = 'mfoud444@gmail.com'; // Replace with your email
|
| 6 |
const password = '009988Ppooii@@@@'; // Replace with your password
|
| 7 |
+
const eventUrl = 'https://webook.com/en/events/ewc-opening-ceremony-2025-tickets/book'; // Replace with your event URL
|
| 8 |
|
| 9 |
const login = new WeBookLogin(email, password);
|
| 10 |
const page = await login.login();
|
|
|
|
| 13 |
process.exit(1);
|
| 14 |
}
|
| 15 |
|
| 16 |
+
const booking = new WeBookBooking(page);
|
| 17 |
+
const booked = await booking.bookEvent(eventUrl);
|
| 18 |
+
if (booked) {
|
| 19 |
+
console.info('Booking succeeded!');
|
| 20 |
+
} else {
|
| 21 |
+
console.error('Booking failed.');
|
| 22 |
+
}
|
| 23 |
}
|
src/webook/webookApi.ts
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import axios, { AxiosResponse } from 'axios';
|
| 2 |
+
|
| 3 |
+
// Type definitions
|
| 4 |
+
export interface EventFilter {
|
| 5 |
+
visibility_not?: string;
|
| 6 |
+
AND?: any[];
|
| 7 |
+
OR?: any[];
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
export interface EventSchedule {
|
| 11 |
+
title?: string;
|
| 12 |
+
openTitle?: string;
|
| 13 |
+
openDateTime?: string;
|
| 14 |
+
closeDateTime?: string;
|
| 15 |
+
openScheduleText?: string;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
export interface EventLocation {
|
| 19 |
+
title?: string;
|
| 20 |
+
address?: string;
|
| 21 |
+
city?: string;
|
| 22 |
+
countryCode?: string;
|
| 23 |
+
seactionHeader?: string;
|
| 24 |
+
location?: {
|
| 25 |
+
lat: number;
|
| 26 |
+
lon: number;
|
| 27 |
+
};
|
| 28 |
+
banner?: {
|
| 29 |
+
title?: string;
|
| 30 |
+
sys?: {
|
| 31 |
+
id: string;
|
| 32 |
+
publishedAt: string;
|
| 33 |
+
};
|
| 34 |
+
url?: string;
|
| 35 |
+
width?: number;
|
| 36 |
+
height?: number;
|
| 37 |
+
contentType?: string;
|
| 38 |
+
};
|
| 39 |
+
accessibility?: any;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
export interface EventZone {
|
| 43 |
+
id?: string;
|
| 44 |
+
slug?: string;
|
| 45 |
+
title?: string;
|
| 46 |
+
zoneLogo?: {
|
| 47 |
+
title?: string;
|
| 48 |
+
sys?: {
|
| 49 |
+
id: string;
|
| 50 |
+
publishedAt: string;
|
| 51 |
+
};
|
| 52 |
+
url?: string;
|
| 53 |
+
width?: number;
|
| 54 |
+
height?: number;
|
| 55 |
+
contentType?: string;
|
| 56 |
+
};
|
| 57 |
+
sponsorLogo?: {
|
| 58 |
+
title?: string;
|
| 59 |
+
sys?: {
|
| 60 |
+
id: string;
|
| 61 |
+
publishedAt: string;
|
| 62 |
+
};
|
| 63 |
+
url?: string;
|
| 64 |
+
width?: number;
|
| 65 |
+
height?: number;
|
| 66 |
+
contentType?: string;
|
| 67 |
+
};
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
export interface EventCategory {
|
| 71 |
+
id?: string;
|
| 72 |
+
title?: string;
|
| 73 |
+
slug?: string;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
export interface EventImage {
|
| 77 |
+
title?: string;
|
| 78 |
+
sys?: {
|
| 79 |
+
id: string;
|
| 80 |
+
publishedAt: string;
|
| 81 |
+
};
|
| 82 |
+
url?: string;
|
| 83 |
+
width?: number;
|
| 84 |
+
height?: number;
|
| 85 |
+
contentType?: string;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
export interface Event {
|
| 89 |
+
__typename?: string;
|
| 90 |
+
sys?: {
|
| 91 |
+
id: string;
|
| 92 |
+
};
|
| 93 |
+
id?: string;
|
| 94 |
+
title?: string;
|
| 95 |
+
subtitle?: string;
|
| 96 |
+
slug?: string;
|
| 97 |
+
ticketingUrlSlug?: string;
|
| 98 |
+
image11?: EventImage;
|
| 99 |
+
image31?: EventImage;
|
| 100 |
+
startingPrice?: number;
|
| 101 |
+
currencyCode?: string;
|
| 102 |
+
schedule?: EventSchedule;
|
| 103 |
+
isStreamingEvent?: boolean;
|
| 104 |
+
zoneEntryIncluded?: boolean;
|
| 105 |
+
streamingUrl?: string;
|
| 106 |
+
buttonLabel?: string;
|
| 107 |
+
cardButtonLabel?: string;
|
| 108 |
+
eventType?: string;
|
| 109 |
+
buttonLink?: string;
|
| 110 |
+
zone?: EventZone;
|
| 111 |
+
location?: EventLocation;
|
| 112 |
+
category?: EventCategory;
|
| 113 |
+
isComingSoon?: boolean;
|
| 114 |
+
organizationSlug?: string;
|
| 115 |
+
carousalCollection?: {
|
| 116 |
+
items?: EventImage[];
|
| 117 |
+
};
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
export interface EventCollection {
|
| 121 |
+
total: number;
|
| 122 |
+
items: Event[];
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
export interface GraphQLResponse {
|
| 126 |
+
data: {
|
| 127 |
+
eventCollection: EventCollection;
|
| 128 |
+
};
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
export interface EventFilters {
|
| 132 |
+
category?: string | string[];
|
| 133 |
+
date_filter?: 'today' | 'tomorrow' | 'this_week';
|
| 134 |
+
price_min?: number;
|
| 135 |
+
price_max?: number;
|
| 136 |
+
zones?: string[];
|
| 137 |
+
country_code?: string;
|
| 138 |
+
limit?: number;
|
| 139 |
+
skip?: number;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
export class WeBookAPI {
|
| 143 |
+
private url: string = "https://cdn.webook.com/";
|
| 144 |
+
private headers = {
|
| 145 |
+
"Accept": "*/*",
|
| 146 |
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
| 147 |
+
"Accept-Language": "en-US,en;q=0.9,ar;q=0.8",
|
| 148 |
+
"Content-Type": "application/json",
|
| 149 |
+
"Origin": "https://webook.com",
|
| 150 |
+
"Referer": "https://webook.com/",
|
| 151 |
+
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
| 152 |
+
};
|
| 153 |
+
|
| 154 |
+
private getEventsPayload(filters: EventFilters = {}): any {
|
| 155 |
+
const {
|
| 156 |
+
category,
|
| 157 |
+
date_filter,
|
| 158 |
+
price_min,
|
| 159 |
+
price_max,
|
| 160 |
+
zones,
|
| 161 |
+
country_code = "SA",
|
| 162 |
+
limit = 20,
|
| 163 |
+
skip = 0
|
| 164 |
+
} = filters;
|
| 165 |
+
|
| 166 |
+
const now = new Date();
|
| 167 |
+
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
| 168 |
+
const endOfWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
|
| 169 |
+
|
| 170 |
+
// Construct Date Filter Clause
|
| 171 |
+
const dateScheduleOrClauses: any[] = [];
|
| 172 |
+
|
| 173 |
+
// Add clause for events without close date or ending from now onwards
|
| 174 |
+
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
| 175 |
+
dateScheduleOrClauses.push({
|
| 176 |
+
"OR": [
|
| 177 |
+
{ "schedule": { "closeDateTime_exists": false } },
|
| 178 |
+
{ "schedule": { "closeDateTime_gte": startOfToday.toISOString() } }
|
| 179 |
+
]
|
| 180 |
+
});
|
| 181 |
+
|
| 182 |
+
// Add specific date range clause based on date_filter
|
| 183 |
+
let dateRangeAndClause: any = {};
|
| 184 |
+
if (date_filter === 'today') {
|
| 185 |
+
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
| 186 |
+
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
| 187 |
+
dateRangeAndClause = {
|
| 188 |
+
"AND": [
|
| 189 |
+
{ "schedule": { "openDateTime_lte": endOfDay.toISOString() } },
|
| 190 |
+
{ "schedule": { "closeDateTime_gte": startOfDay.toISOString() } }
|
| 191 |
+
]
|
| 192 |
+
};
|
| 193 |
+
} else if (date_filter === 'tomorrow') {
|
| 194 |
+
const tomorrowStart = new Date(tomorrow.getFullYear(), tomorrow.getMonth(), tomorrow.getDate());
|
| 195 |
+
const tomorrowEnd = new Date(tomorrow.getFullYear(), tomorrow.getMonth(), tomorrow.getDate(), 23, 59, 59, 999);
|
| 196 |
+
dateRangeAndClause = {
|
| 197 |
+
"AND": [
|
| 198 |
+
{ "schedule": { "openDateTime_lte": tomorrowEnd.toISOString() } },
|
| 199 |
+
{ "schedule": { "closeDateTime_gte": tomorrowStart.toISOString() } }
|
| 200 |
+
]
|
| 201 |
+
};
|
| 202 |
+
} else if (date_filter === 'this_week') {
|
| 203 |
+
const startOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
| 204 |
+
const endOfWeekDate = new Date(endOfWeek.getFullYear(), endOfWeek.getMonth(), endOfWeek.getDate(), 23, 59, 59, 999);
|
| 205 |
+
dateRangeAndClause = {
|
| 206 |
+
"AND": [
|
| 207 |
+
{ "schedule": { "openDateTime_lte": endOfWeekDate.toISOString() } },
|
| 208 |
+
{ "schedule": { "closeDateTime_gte": startOfWeek.toISOString() } }
|
| 209 |
+
]
|
| 210 |
+
};
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
if (Object.keys(dateRangeAndClause).length > 0) {
|
| 214 |
+
dateScheduleOrClauses.push(dateRangeAndClause);
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
// Construct Category Filter Clause
|
| 218 |
+
let categoryClause: any = {};
|
| 219 |
+
if (category) {
|
| 220 |
+
const categoryArray = Array.isArray(category) ? category : [category];
|
| 221 |
+
categoryClause = { "OR": [{ "category": { "slug_in": categoryArray } }] };
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
// Construct Price Filter Clause
|
| 225 |
+
let priceClause: any = {};
|
| 226 |
+
const priceAndClauses: any[] = [];
|
| 227 |
+
if (price_min !== undefined) {
|
| 228 |
+
priceAndClauses.push({ "startingPrice_gte": price_min });
|
| 229 |
+
}
|
| 230 |
+
if (price_max !== undefined) {
|
| 231 |
+
priceAndClauses.push({ "startingPrice_lte": price_max });
|
| 232 |
+
}
|
| 233 |
+
if (priceAndClauses.length > 0) {
|
| 234 |
+
priceClause = { "AND": priceAndClauses };
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
// Construct Zone Filter Clause
|
| 238 |
+
let zoneClause: any = {};
|
| 239 |
+
if (zones && zones.length > 0) {
|
| 240 |
+
zoneClause = { "OR": [{ "zone": { "slug_in": zones } }] };
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
// Construct Country Filter Clause
|
| 244 |
+
let countryClause: any = {};
|
| 245 |
+
if (country_code) {
|
| 246 |
+
countryClause = { "OR": [{ "location": { "countryCode": country_code } }] };
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
// Build the main 'AND' array for the 'where' clause
|
| 250 |
+
const andClauses: any[] = [];
|
| 251 |
+
|
| 252 |
+
if (Object.keys(zoneClause).length > 0) {
|
| 253 |
+
andClauses.push(zoneClause);
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
if (Object.keys(categoryClause).length > 0) {
|
| 257 |
+
andClauses.push(categoryClause);
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
andClauses.push({}); // Placeholder
|
| 261 |
+
|
| 262 |
+
if (Object.keys(priceClause).length > 0) {
|
| 263 |
+
andClauses.push(priceClause);
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
andClauses.push({}); // Placeholder
|
| 267 |
+
|
| 268 |
+
// Add the combined date schedule OR clause
|
| 269 |
+
if (dateScheduleOrClauses.length > 0) {
|
| 270 |
+
if (dateScheduleOrClauses.length === 1 && "OR" in dateScheduleOrClauses[0]) {
|
| 271 |
+
andClauses.push(dateScheduleOrClauses[0]);
|
| 272 |
+
} else {
|
| 273 |
+
andClauses.push({ "OR": dateScheduleOrClauses });
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
if (Object.keys(countryClause).length > 0) {
|
| 278 |
+
andClauses.push(countryClause);
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
// Construct the final 'where' clause
|
| 282 |
+
const whereClause: EventFilter = {
|
| 283 |
+
"visibility_not": "private",
|
| 284 |
+
};
|
| 285 |
+
|
| 286 |
+
if (andClauses.length > 0) {
|
| 287 |
+
whereClause.AND = andClauses;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
return {
|
| 291 |
+
"query": `query getEventListing($lang:String,$limit:Int,$skip:Int,$where:EventFilter,$order:[EventOrder]){
|
| 292 |
+
eventCollection(locale:$lang,limit:$limit,skip:$skip,where:$where,order:$order){
|
| 293 |
+
total items{
|
| 294 |
+
__typename sys{id}id title subtitle slug ticketingUrlSlug
|
| 295 |
+
image11{title sys{id publishedAt}url width height contentType}
|
| 296 |
+
image31{title sys{id publishedAt}url width height contentType}
|
| 297 |
+
startingPrice currencyCode
|
| 298 |
+
schedule{title openTitle openDateTime closeDateTime openScheduleText}
|
| 299 |
+
isStreamingEvent zoneEntryIncluded streamingUrl buttonLabel cardButtonLabel
|
| 300 |
+
eventType buttonLink
|
| 301 |
+
zone{id slug title zoneLogo{title sys{id publishedAt}url width height contentType}
|
| 302 |
+
sponsorLogo{title sys{id publishedAt}url width height contentType}}
|
| 303 |
+
location{title address city countryCode seactionHeader location{lat lon}
|
| 304 |
+
banner{title sys{id publishedAt}url width height contentType} accessibility}
|
| 305 |
+
category{id title slug} isComingSoon organizationSlug
|
| 306 |
+
carousalCollection(limit:10){
|
| 307 |
+
items{title sys{id publishedAt}url width height contentType}
|
| 308 |
+
}
|
| 309 |
+
}
|
| 310 |
+
}
|
| 311 |
+
}`,
|
| 312 |
+
"variables": {
|
| 313 |
+
"order": ["sys_publishedAt_DESC"],
|
| 314 |
+
"lang": "ar-SA",
|
| 315 |
+
"limit": limit,
|
| 316 |
+
"skip": skip,
|
| 317 |
+
"where": whereClause,
|
| 318 |
+
},
|
| 319 |
+
"operationName": "getEventListing"
|
| 320 |
+
};
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
async getEvents(filters: EventFilters = {}): Promise<GraphQLResponse> {
|
| 324 |
+
try {
|
| 325 |
+
const payload = this.getEventsPayload(filters);
|
| 326 |
+
const response: AxiosResponse<GraphQLResponse> = await axios.post(
|
| 327 |
+
this.url,
|
| 328 |
+
payload,
|
| 329 |
+
{ headers: this.headers }
|
| 330 |
+
);
|
| 331 |
+
return response.data;
|
| 332 |
+
} catch (error) {
|
| 333 |
+
console.error('Error fetching events:', error);
|
| 334 |
+
throw error;
|
| 335 |
+
}
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
async makeRequest(payload: any): Promise<any> {
|
| 339 |
+
try {
|
| 340 |
+
const response = await axios.post(this.url, payload, { headers: this.headers });
|
| 341 |
+
return response.data;
|
| 342 |
+
} catch (error) {
|
| 343 |
+
console.error('Error making request:', error);
|
| 344 |
+
throw error;
|
| 345 |
+
}
|
| 346 |
+
}
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
// Export a default instance
|
| 350 |
+
export const webookAPI = new WeBookAPI();
|
src/webook/webookBase.ts
CHANGED
|
@@ -32,7 +32,7 @@ export class WeBookBase {
|
|
| 32 |
if (!this.page) {
|
| 33 |
const headless = process.env.PLAYWRIGHT_HEADLESS !== 'false';
|
| 34 |
this.browser = await chromium.launchPersistentContext(this.profileDir, {
|
| 35 |
-
headless:
|
| 36 |
args: [],
|
| 37 |
});
|
| 38 |
this.page = this.browser.pages()[0] || await this.browser.newPage();
|
|
|
|
| 32 |
if (!this.page) {
|
| 33 |
const headless = process.env.PLAYWRIGHT_HEADLESS !== 'false';
|
| 34 |
this.browser = await chromium.launchPersistentContext(this.profileDir, {
|
| 35 |
+
headless: headless,
|
| 36 |
args: [],
|
| 37 |
});
|
| 38 |
this.page = this.browser.pages()[0] || await this.browser.newPage();
|