|
|
import logging |
|
|
from telegram import CallbackQuery, Update, InlineKeyboardButton, InlineKeyboardMarkup |
|
|
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes, MessageHandler, filters |
|
|
import requests |
|
|
import json |
|
|
from datetime import datetime, timedelta |
|
|
from monitor import EventMonitor |
|
|
from dotenv import load_dotenv |
|
|
import os |
|
|
import asyncio |
|
|
|
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
level=logging.INFO, |
|
|
format='%(asctime)s - %(levelname)s - %(message)s', |
|
|
handlers=[ |
|
|
logging.FileHandler('logs/telegram_bot.log'), |
|
|
logging.StreamHandler() |
|
|
] |
|
|
) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
class WeBookBot: |
|
|
def __init__(self, token): |
|
|
self.token = token |
|
|
self.url = "https://cdn.webook.com/" |
|
|
self.headers = { |
|
|
"Accept": "*/*", |
|
|
"Accept-Encoding": "gzip, deflate, br, zstd", |
|
|
"Accept-Language": "en-US,en;q=0.9,ar;q=0.8", |
|
|
"Content-Type": "application/json", |
|
|
"Origin": "https://webook.com", |
|
|
"Referer": "https://webook.com/", |
|
|
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", |
|
|
} |
|
|
self.monitor = EventMonitor(self) |
|
|
self.user_credentials = {} |
|
|
self.application = None |
|
|
self.is_running = False |
|
|
|
|
|
def _get_events_payload(self, category=None, date_filter=None, price_min=None, price_max=None, zones=None, country_code="SA", limit=20, skip=0): |
|
|
"""Generate the payload for the events API request with flexible filtering using the getShows query.""" |
|
|
now = datetime.now() |
|
|
tomorrow = now + timedelta(days=1) |
|
|
end_of_week = now + timedelta(days=7) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
date_schedule_or_clauses = [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start_of_today = now.replace(hour=0, minute=0, second=0, microsecond=0) |
|
|
date_schedule_or_clauses.append({ |
|
|
"OR": [ |
|
|
{"schedule":{"closeDateTime_exists":False}}, |
|
|
{"schedule":{"closeDateTime_gte": start_of_today.strftime("%Y-%m-%dT%H:%M:%S.000Z")}} |
|
|
] |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
date_range_and_clause = {} |
|
|
if date_filter == 'today': |
|
|
start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0) |
|
|
end_of_day = now.replace(hour=23, minute=59, second=59, microsecond=999999) |
|
|
|
|
|
date_range_and_clause = { |
|
|
"AND": [ |
|
|
{"schedule": {"openDateTime_lte": end_of_day.strftime("%Y-%m-%dT%H:%M:%S.000Z")}}, |
|
|
{"schedule": {"closeDateTime_gte": start_of_day.strftime("%Y-%m-%dT%H:%M:%S.000Z")}} |
|
|
] |
|
|
} |
|
|
elif date_filter == 'tomorrow': |
|
|
tomorrow_start = tomorrow.replace(hour=0, minute=0, second=0, microsecond=0) |
|
|
tomorrow_end = tomorrow.replace(hour=23, minute=59, second=59, microsecond=999999) |
|
|
|
|
|
date_range_and_clause = { |
|
|
"AND": [ |
|
|
{"schedule": {"openDateTime_lte": tomorrow_end.strftime("%Y-%m-%dT%H:%M:%S.000Z")}}, |
|
|
{"schedule": {"closeDateTime_gte": tomorrow_start.strftime("%Y-%m-%dT%H:%M:%S.000Z")}} |
|
|
] |
|
|
} |
|
|
elif date_filter == 'this_week': |
|
|
start_of_week = now.replace(hour=0, minute=0, second=0, microsecond=0) |
|
|
end_of_week = end_of_week.replace(hour=23, minute=59, second=59, microsecond=999999) |
|
|
|
|
|
date_range_and_clause = { |
|
|
"AND": [ |
|
|
{"schedule": {"openDateTime_lte": end_of_week.strftime("%Y-%m-%dT%H:%M:%S.000Z")}}, |
|
|
{"schedule": {"closeDateTime_gte": start_of_week.strftime("%Y-%m-%dT%H:%M:%S.000Z")}} |
|
|
] |
|
|
} |
|
|
|
|
|
if date_range_and_clause: |
|
|
date_schedule_or_clauses.append(date_range_and_clause) |
|
|
|
|
|
|
|
|
|
|
|
category_clause = {} |
|
|
if category: |
|
|
if isinstance(category, str): |
|
|
category = [category] |
|
|
|
|
|
category_clause = {"OR": [{"category": {"slug_in": category}}]} |
|
|
|
|
|
|
|
|
|
|
|
price_clause = {} |
|
|
price_and_clauses = [] |
|
|
if price_min is not None: |
|
|
price_and_clauses.append({"startingPrice_gte": price_min}) |
|
|
if price_max is not None: |
|
|
price_and_clauses.append({"startingPrice_lte": price_max}) |
|
|
if price_and_clauses: |
|
|
price_clause = {"AND": price_and_clauses} |
|
|
|
|
|
|
|
|
|
|
|
zone_clause = {} |
|
|
if zones: |
|
|
zone_clause = {"OR":[{"zone":{"slug_in": zones}}]} |
|
|
|
|
|
|
|
|
country_clause = {} |
|
|
if country_code: |
|
|
country_clause = {"OR":[{"location":{"countryCode": country_code}}]} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
and_clauses = [] |
|
|
|
|
|
|
|
|
if zone_clause: |
|
|
and_clauses.append(zone_clause) |
|
|
|
|
|
|
|
|
if category_clause: |
|
|
and_clauses.append(category_clause) |
|
|
|
|
|
and_clauses.append({}) |
|
|
|
|
|
|
|
|
if price_clause: |
|
|
and_clauses.append(price_clause) |
|
|
|
|
|
and_clauses.append({}) |
|
|
|
|
|
|
|
|
if date_schedule_or_clauses: |
|
|
|
|
|
if len(date_schedule_or_clauses) == 1 and "OR" in date_schedule_or_clauses[0]: |
|
|
and_clauses.append(date_schedule_or_clauses[0]) |
|
|
else: |
|
|
and_clauses.append({"OR": date_schedule_or_clauses}) |
|
|
|
|
|
|
|
|
if country_clause: |
|
|
and_clauses.append(country_clause) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
where_clause = { |
|
|
"visibility_not": "private", |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
if and_clauses: |
|
|
where_clause["AND"] = and_clauses |
|
|
|
|
|
|
|
|
return { |
|
|
"query": """query getEventListing($lang:String,$limit:Int,$skip:Int,$where:EventFilter,$order:[EventOrder]){ |
|
|
eventCollection(locale:$lang,limit:$limit,skip:$skip,where:$where,order:$order){ |
|
|
total items{ |
|
|
__typename sys{id}id title subtitle slug ticketingUrlSlug |
|
|
image11{title sys{id publishedAt}url width height contentType} |
|
|
image31{title sys{id publishedAt}url width height contentType} |
|
|
startingPrice currencyCode |
|
|
schedule{title openTitle openDateTime closeDateTime openScheduleText} |
|
|
isStreamingEvent zoneEntryIncluded streamingUrl buttonLabel cardButtonLabel |
|
|
eventType buttonLink |
|
|
zone{id slug title zoneLogo{title sys{id publishedAt}url width height contentType} |
|
|
sponsorLogo{title sys{id publishedAt}url width height contentType}} |
|
|
location{title address city countryCode seactionHeader location{lat lon} |
|
|
banner{title sys{id publishedAt}url width height contentType} accessibility} |
|
|
category{id title slug} isComingSoon organizationSlug |
|
|
carousalCollection(limit:10){ |
|
|
items{title sys{id publishedAt}url width height contentType} |
|
|
} |
|
|
} |
|
|
} |
|
|
}""", |
|
|
"variables": { |
|
|
"order": ["sys_publishedAt_DESC"], |
|
|
"lang": "ar-SA", |
|
|
"limit": limit, |
|
|
"skip": skip, |
|
|
"where": where_clause, |
|
|
}, |
|
|
"operationName": "getEventListing" |
|
|
} |
|
|
|
|
|
async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Handle the /start command""" |
|
|
keyboard = [ |
|
|
[ |
|
|
InlineKeyboardButton("๐ Login to WeBook", callback_data='login_webook'), |
|
|
InlineKeyboardButton("โน๏ธ Help", callback_data='help') |
|
|
], |
|
|
[ |
|
|
InlineKeyboardButton("๐ค Set Account Info", callback_data='set_account_info'), |
|
|
InlineKeyboardButton("๐ Account Status", callback_data='account_status') |
|
|
], |
|
|
[ |
|
|
InlineKeyboardButton("๐ซ Browse Events", callback_data='browse_events') |
|
|
] |
|
|
] |
|
|
reply_markup = InlineKeyboardMarkup(keyboard) |
|
|
|
|
|
await update.message.reply_text( |
|
|
'Welcome to WeBook Events Bot! ๐\n' |
|
|
'Please select an option:', |
|
|
reply_markup=reply_markup |
|
|
) |
|
|
|
|
|
async def set_account_info_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Handle the /setinfoaccountwebook command""" |
|
|
await update.message.reply_text( |
|
|
"Please enter your WeBook credentials in the following format:\n" |
|
|
"email:your.email@example.com\n" |
|
|
"password:yourpassword" |
|
|
) |
|
|
context.user_data['awaiting_credentials'] = True |
|
|
|
|
|
async def account_status_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Handle the /stateloginwebook command""" |
|
|
user_id = update.effective_user.id |
|
|
if user_id in self.user_credentials: |
|
|
await update.message.reply_text( |
|
|
f"โ
WeBook Account Status:\n" |
|
|
f"Email: {self.user_credentials[user_id]['email']}\n" |
|
|
f"Status: Logged In" |
|
|
) |
|
|
else: |
|
|
await update.message.reply_text( |
|
|
"โ No WeBook account configured.\n" |
|
|
"Use /setinfoaccountwebook to set up your account." |
|
|
) |
|
|
|
|
|
async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Handle the /help command""" |
|
|
help_text = ( |
|
|
"๐ค *WeBook Bot Commands*\n\n" |
|
|
"/start - Start the bot and show main menu\n" |
|
|
"/setinfoaccountwebook - Set your WeBook account credentials\n" |
|
|
"/stateloginwebook - Check your WeBook account status\n" |
|
|
"/help - Show this help message\n" |
|
|
"/info - Show bot information\n\n" |
|
|
"To use the bot:\n" |
|
|
"1. Set your WeBook credentials using /setinfoaccountwebook\n" |
|
|
"2. Login using the 'Login to WeBook' button\n" |
|
|
"3. Browse and book events!" |
|
|
) |
|
|
await update.message.reply_text(help_text, parse_mode='Markdown') |
|
|
|
|
|
async def info_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Handle the /info command""" |
|
|
info_text = ( |
|
|
"๐ค *WeBook Bot Information*\n\n" |
|
|
"Version: 1.0.0\n" |
|
|
"Description: A Telegram bot for browsing and booking events on WeBook\n" |
|
|
"Features:\n" |
|
|
"- Browse events by category\n" |
|
|
"- Filter events by date\n" |
|
|
"- Book events automatically\n" |
|
|
"- Monitor for new events\n" |
|
|
"- Account management" |
|
|
) |
|
|
await update.message.reply_text(info_text, parse_mode='Markdown') |
|
|
|
|
|
async def handle_credentials_input(self, update: Update, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Handle user input for WeBook credentials""" |
|
|
if not context.user_data.get('awaiting_credentials'): |
|
|
return |
|
|
|
|
|
try: |
|
|
text = update.message.text.strip() |
|
|
email = None |
|
|
password = None |
|
|
|
|
|
for line in text.split('\n'): |
|
|
if line.startswith('email:'): |
|
|
email = line.split(':', 1)[1].strip() |
|
|
elif line.startswith('password:'): |
|
|
password = line.split(':', 1)[1].strip() |
|
|
|
|
|
if email and password: |
|
|
user_id = update.effective_user.id |
|
|
self.user_credentials[user_id] = { |
|
|
'email': email, |
|
|
'password': password |
|
|
} |
|
|
context.user_data['awaiting_credentials'] = False |
|
|
await update.message.reply_text( |
|
|
"โ
WeBook credentials saved successfully!\n" |
|
|
"You can now use the 'Login to WeBook' button to log in." |
|
|
) |
|
|
else: |
|
|
await update.message.reply_text( |
|
|
"โ Invalid format. Please use:\n" |
|
|
"email:your.email@example.com\n" |
|
|
"password:yourpassword" |
|
|
) |
|
|
except Exception as e: |
|
|
logger.error(f"Error handling credentials: {e}") |
|
|
await update.message.reply_text("โ Error saving credentials. Please try again.") |
|
|
|
|
|
async def handle_login(self, query: CallbackQuery, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Handle WeBook login button click""" |
|
|
user_id = query.from_user.id |
|
|
if user_id not in self.user_credentials: |
|
|
await query.edit_message_text( |
|
|
"โ No WeBook credentials found.\n" |
|
|
"Please use /setinfoaccountwebook to set up your account first." |
|
|
) |
|
|
return |
|
|
|
|
|
try: |
|
|
|
|
|
login = WeBookLogin( |
|
|
email=self.user_credentials[user_id]['email'], |
|
|
password=self.user_credentials[user_id]['password'] |
|
|
) |
|
|
|
|
|
|
|
|
page = login.login() |
|
|
|
|
|
if page: |
|
|
await query.edit_message_text( |
|
|
"โ
Successfully logged in to WeBook!\n" |
|
|
"You can now browse and book events." |
|
|
) |
|
|
|
|
|
context.user_data['webook_page'] = page |
|
|
else: |
|
|
await query.edit_message_text( |
|
|
"โ Login failed. Please check your credentials and try again." |
|
|
) |
|
|
except Exception as e: |
|
|
logger.error(f"Login error: {e}") |
|
|
await query.edit_message_text( |
|
|
"โ An error occurred during login. Please try again later." |
|
|
) |
|
|
|
|
|
async def button_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Handle button callbacks""" |
|
|
query = update.callback_query |
|
|
await query.answer() |
|
|
|
|
|
try: |
|
|
callback_data = query.data |
|
|
|
|
|
if callback_data == 'login_webook': |
|
|
await self.handle_login(query, context) |
|
|
elif callback_data == 'help': |
|
|
await self.help_command(update, context) |
|
|
elif callback_data == 'set_account_info': |
|
|
await self.set_account_info_command(update, context) |
|
|
elif callback_data == 'account_status': |
|
|
await self.account_status_command(update, context) |
|
|
elif callback_data == 'browse_events': |
|
|
|
|
|
await self.start_command(update, context) |
|
|
elif callback_data.startswith('category_'): |
|
|
selected_category = callback_data.replace('category_', '') |
|
|
|
|
|
if context.user_data.get('category') == selected_category: |
|
|
context.user_data['category'] = None |
|
|
else: |
|
|
context.user_data['category'] = selected_category |
|
|
|
|
|
current_filters_text = self._get_current_filters_text(context.user_data) |
|
|
await query.edit_message_text( |
|
|
f'Welcome to WeBook Events Bot! ๐\n' |
|
|
f'Please select filters or "Show Events":\n{current_filters_text}', |
|
|
reply_markup=query.message.reply_markup |
|
|
) |
|
|
return |
|
|
|
|
|
elif callback_data.startswith('date_'): |
|
|
selected_date_filter = callback_data.replace('date_', '') |
|
|
|
|
|
if context.user_data.get('date_filter') == selected_date_filter: |
|
|
|
|
|
|
|
|
pass |
|
|
else: |
|
|
context.user_data['date_filter'] = selected_date_filter |
|
|
|
|
|
current_filters_text = self._get_current_filters_text(context.user_data) |
|
|
await query.edit_message_text( |
|
|
f'Welcome to WeBook Events Bot! ๐\n' |
|
|
f'Please select filters or "Show Events":\n{current_filters_text}', |
|
|
reply_markup=query.message.reply_markup |
|
|
) |
|
|
return |
|
|
|
|
|
elif callback_data == 'reset_filters': |
|
|
|
|
|
context.user_data['category'] = None |
|
|
context.user_data['date_filter'] = 'tomorrow' |
|
|
context.user_data['price_min'] = None |
|
|
context.user_data['price_max'] = None |
|
|
context.user_data['zones'] = None |
|
|
context.user_data['country_code'] = 'SA' |
|
|
|
|
|
current_filters_text = self._get_current_filters_text(context.user_data) |
|
|
await query.edit_message_text( |
|
|
f'Welcome to WeBook Events Bot! ๐\n' |
|
|
f'Filters have been reset. Please select filters or "Show Events":\n{current_filters_text}', |
|
|
reply_markup=query.message.reply_markup |
|
|
) |
|
|
return |
|
|
|
|
|
|
|
|
elif callback_data.startswith('page_'): |
|
|
try: |
|
|
|
|
|
new_skip = int(callback_data.replace('page_', '')) |
|
|
|
|
|
current_category = context.user_data.get('category') |
|
|
current_date_filter = context.user_data.get('date_filter') |
|
|
current_price_min = context.user_data.get('price_min') |
|
|
current_price_max = context.user_data.get('price_max') |
|
|
current_zones = context.user_data.get('zones') |
|
|
current_country_code = context.user_data.get('country_code') |
|
|
current_limit = 10 |
|
|
|
|
|
|
|
|
context.user_data['skip'] = new_skip |
|
|
|
|
|
|
|
|
payload = self._get_events_payload( |
|
|
category=current_category, |
|
|
date_filter=current_date_filter, |
|
|
price_min=current_price_min, |
|
|
price_max=current_price_max, |
|
|
zones=current_zones, |
|
|
country_code=current_country_code, |
|
|
limit=current_limit, |
|
|
skip=new_skip |
|
|
) |
|
|
|
|
|
response = requests.post(self.url, headers=self.headers, json=payload) |
|
|
response.raise_for_status() |
|
|
|
|
|
events = response.json()["data"]["eventCollection"]["items"] |
|
|
total_events = response.json()["data"]["eventCollection"]["total"] |
|
|
|
|
|
|
|
|
filter_info = self._get_current_filters_text(context.user_data) |
|
|
|
|
|
if not events: |
|
|
|
|
|
await query.edit_message_text(f"No events found for this page.\n{filter_info}") |
|
|
return |
|
|
|
|
|
|
|
|
message_parts = [f"๐ซ Upcoming Events (Filtered - Page {int(new_skip/current_limit) + 1}/{int(total_events/current_limit) + (1 if total_events % current_limit > 0 else 0)} of {total_events}):\n{filter_info}\n"] |
|
|
|
|
|
for i, event in enumerate(events): |
|
|
|
|
|
event_number = new_skip + i + 1 |
|
|
message_parts.append(f"--- Event {event_number} ---") |
|
|
message_parts.append(f"๐ *{event.get('title', 'N/A')}*") |
|
|
if event.get('subtitle'): |
|
|
message_parts.append(f"_{event.get('subtitle')}_") |
|
|
|
|
|
location_title = event.get('location', {}).get('title', 'N/A') |
|
|
message_parts.append(f"๐ Location: {location_title}") |
|
|
|
|
|
price = event.get('startingPrice', 'N/A') |
|
|
currency = event.get('currencyCode', 'N/A') |
|
|
message_parts.append(f"๐ฐ Starting Price: {price} {currency}") |
|
|
|
|
|
|
|
|
open_date = event.get('schedule', {}).get('openDateTime') |
|
|
close_date = event.get('schedule', {}).get('closeDateTime') |
|
|
schedule_text = "๐ Schedule: " |
|
|
if open_date: |
|
|
try: |
|
|
open_dt = datetime.fromisoformat(open_date.replace('Z', '+00:00')) |
|
|
schedule_text += f"Starts: {open_dt.strftime('%Y-%m-%d %H:%M')}" |
|
|
except ValueError: |
|
|
schedule_text += f"Starts: {open_date}" |
|
|
if close_date: |
|
|
try: |
|
|
close_dt = datetime.fromisoformat(close_date.replace('Z', '+00:00')) |
|
|
schedule_text += f", Ends: {close_dt.strftime('%Y-%m-%d %H:%M')}" |
|
|
except ValueError: |
|
|
schedule_text += f", Ends: {close_date}" |
|
|
if not open_date and not close_date: |
|
|
schedule_text += "Info Not Available" |
|
|
message_parts.append(schedule_text) |
|
|
|
|
|
|
|
|
ticketing_slug = event.get('ticketingUrlSlug') |
|
|
if ticketing_slug and ticketing_slug != 'N/A': |
|
|
message_parts.append(f"[More Info/Tickets](https://webook.com/ar/events/{ticketing_slug})") |
|
|
|
|
|
|
|
|
message_parts.append("") |
|
|
|
|
|
|
|
|
pagination_buttons = [] |
|
|
|
|
|
if new_skip > 0: |
|
|
pagination_buttons.append(InlineKeyboardButton("โฌ
๏ธ Back", callback_data=f'page_{max(0, new_skip - current_limit)}')) |
|
|
|
|
|
if new_skip + len(events) < total_events: |
|
|
pagination_buttons.append(InlineKeyboardButton("โก๏ธ Next", callback_data=f'page_{new_skip + current_limit}')) |
|
|
|
|
|
|
|
|
select_button = [InlineKeyboardButton("๐ Select Event by Number", callback_data='prompt_select_event')] |
|
|
|
|
|
|
|
|
keyboard = [pagination_buttons] if pagination_buttons else [] |
|
|
keyboard.append(select_button) |
|
|
reply_markup = InlineKeyboardMarkup(keyboard) |
|
|
|
|
|
|
|
|
|
|
|
message = "\n".join(message_parts) |
|
|
|
|
|
|
|
|
await query.edit_message_text(text=message, reply_markup=reply_markup, parse_mode='Markdown', disable_web_page_preview=True) |
|
|
|
|
|
except (ValueError, TypeError) as e: |
|
|
logger.error(f"Error parsing page number from callback data: {e}") |
|
|
await query.edit_message_text("Sorry, there was an error processing the page request.") |
|
|
except Exception as e: |
|
|
logger.error(f"Error fetching paginated events: {str(e)}") |
|
|
await query.edit_message_text( |
|
|
"Sorry, there was an error fetching the next page of events. Please try again later." |
|
|
) |
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
elif callback_data == 'show_events': |
|
|
|
|
|
current_category = context.user_data.get('category') |
|
|
current_date_filter = context.user_data.get('date_filter') |
|
|
current_price_min = context.user_data.get('price_min') |
|
|
current_price_max = context.user_data.get('price_max') |
|
|
current_zones = context.user_data.get('zones') |
|
|
current_country_code = context.user_data.get('country_code') |
|
|
|
|
|
|
|
|
context.user_data['skip'] = 0 |
|
|
current_limit = 10 |
|
|
|
|
|
payload = self._get_events_payload( |
|
|
category=current_category, |
|
|
date_filter=current_date_filter, |
|
|
price_min=current_price_min, |
|
|
price_max=current_price_max, |
|
|
zones=current_zones, |
|
|
country_code=current_country_code, |
|
|
limit=current_limit, |
|
|
skip=0 |
|
|
) |
|
|
|
|
|
response = requests.post(self.url, headers=self.headers, json=payload) |
|
|
response.raise_for_status() |
|
|
|
|
|
events = response.json()["data"]["eventCollection"]["items"] |
|
|
total_events = response.json()["data"]["eventCollection"]["total"] |
|
|
|
|
|
|
|
|
filter_info = self._get_current_filters_text(context.user_data) |
|
|
|
|
|
if not events: |
|
|
await query.edit_message_text(f"No events found for the selected filters.\n{filter_info}") |
|
|
return |
|
|
|
|
|
|
|
|
context.user_data['total_events'] = total_events |
|
|
|
|
|
context.user_data['current_event_list'] = events |
|
|
|
|
|
|
|
|
|
|
|
message_parts = [f"๐ซ Upcoming Events (Filtered - Page 1/{int(total_events/current_limit) + (1 if total_events % current_limit > 0 else 0)} of {total_events}):\n{filter_info}\n"] |
|
|
|
|
|
for i, event in enumerate(events): |
|
|
|
|
|
event_number = context.user_data['skip'] + i + 1 |
|
|
message_parts.append(f"--- Event {event_number} ---") |
|
|
message_parts.append(f"๐ *{event.get('title', 'N/A')}*") |
|
|
if event.get('subtitle'): |
|
|
message_parts.append(f"_{event.get('subtitle')}_") |
|
|
|
|
|
location_title = event.get('location', {}).get('title', 'N/A') |
|
|
message_parts.append(f"๐ Location: {location_title}") |
|
|
|
|
|
price = event.get('startingPrice', 'N/A') |
|
|
currency = event.get('currencyCode', 'N/A') |
|
|
message_parts.append(f"๐ฐ Starting Price: {price} {currency}") |
|
|
|
|
|
|
|
|
open_date = event.get('schedule', {}).get('openDateTime') |
|
|
close_date = event.get('schedule', {}).get('closeDateTime') |
|
|
schedule_text = "๐ Schedule: " |
|
|
if open_date: |
|
|
try: |
|
|
open_dt = datetime.fromisoformat(open_date.replace('Z', '+00:00')) |
|
|
schedule_text += f"Starts: {open_dt.strftime('%Y-%m-%d %H:%M')}" |
|
|
except ValueError: |
|
|
schedule_text += f"Starts: {open_date}" |
|
|
if close_date: |
|
|
try: |
|
|
close_dt = datetime.fromisoformat(close_date.replace('Z', '+00:00')) |
|
|
schedule_text += f", Ends: {close_dt.strftime('%Y-%m-%d %H:%M')}" |
|
|
except ValueError: |
|
|
schedule_text += f", Ends: {close_date}" |
|
|
if not open_date and not close_date: |
|
|
schedule_text += "Info Not Available" |
|
|
message_parts.append(schedule_text) |
|
|
|
|
|
|
|
|
ticketing_slug = event.get('ticketingUrlSlug') |
|
|
if ticketing_slug and ticketing_slug != 'N/A': |
|
|
message_parts.append(f"[More Info/Tickets](https://webook.com/ar/events/{ticketing_slug})") |
|
|
|
|
|
|
|
|
message_parts.append("") |
|
|
|
|
|
|
|
|
pagination_buttons = [] |
|
|
|
|
|
if len(events) < total_events: |
|
|
pagination_buttons.append(InlineKeyboardButton("โก๏ธ Next", callback_data=f'page_{current_limit}')) |
|
|
|
|
|
|
|
|
select_button = [InlineKeyboardButton("๐ Select Event by Number", callback_data='prompt_select_event')] |
|
|
|
|
|
|
|
|
keyboard = [pagination_buttons] if pagination_buttons else [] |
|
|
keyboard.append(select_button) |
|
|
reply_markup = InlineKeyboardMarkup(keyboard) |
|
|
|
|
|
|
|
|
|
|
|
message = "\n".join(message_parts) |
|
|
|
|
|
|
|
|
await query.edit_message_text(text=message, reply_markup=reply_markup, parse_mode='Markdown', disable_web_page_preview=True) |
|
|
|
|
|
|
|
|
|
|
|
elif callback_data == 'prompt_select_event': |
|
|
await query.edit_message_text( |
|
|
f"{query.message.text_markdown_v2}\n\n" |
|
|
f"Please reply with the number of the event you want to select (e.g., `1`, `5`, `12`)." |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
) |
|
|
|
|
|
context.user_data['awaiting_event_number'] = True |
|
|
|
|
|
elif callback_data == 'start_monitoring': |
|
|
|
|
|
await self._start_monitoring_setup(query, context) |
|
|
elif callback_data == 'stop_monitoring': |
|
|
|
|
|
user_id = query.from_user.id |
|
|
if await self.monitor.stop_monitoring(user_id): |
|
|
await query.edit_message_text( |
|
|
"โ
Event monitoring stopped successfully." |
|
|
) |
|
|
else: |
|
|
await query.edit_message_text( |
|
|
"โ No active monitoring found." |
|
|
) |
|
|
else: |
|
|
|
|
|
await query.edit_message_text("Unknown button pressed.") |
|
|
return |
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error processing callback: {str(e)}") |
|
|
logger.error(f"Response content: {response.text if 'response' in locals() else 'No response'}") |
|
|
await query.edit_message_text( |
|
|
"Sorry, there was an error processing your request. Please try again later." |
|
|
) |
|
|
|
|
|
async def _start_monitoring_setup(self, query: CallbackQuery, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Start the monitoring setup process""" |
|
|
keyboard = [ |
|
|
[InlineKeyboardButton("๐ Select Country", callback_data='monitor_country')], |
|
|
[InlineKeyboardButton("๐
Select Date Filter", callback_data='monitor_date')], |
|
|
[InlineKeyboardButton("๐ฏ Select Category", callback_data='monitor_category')], |
|
|
[InlineKeyboardButton("๐ฐ Set Price Range", callback_data='monitor_price')], |
|
|
[InlineKeyboardButton("โ
Start Monitoring", callback_data='confirm_monitoring')] |
|
|
] |
|
|
|
|
|
reply_markup = InlineKeyboardMarkup(keyboard) |
|
|
await query.edit_message_text( |
|
|
"๐ *Event Monitoring Setup*\n\n" |
|
|
"Please configure your monitoring preferences:", |
|
|
reply_markup=reply_markup, |
|
|
parse_mode='Markdown' |
|
|
) |
|
|
|
|
|
|
|
|
context.user_data['monitoring_filters'] = { |
|
|
'country_code': 'SA', |
|
|
'date_filter': 'today', |
|
|
'category': None, |
|
|
'price_min': None, |
|
|
'price_max': None |
|
|
} |
|
|
|
|
|
def _get_current_filters_text(self, user_data): |
|
|
"""Helper function to generate text summarizing current filters.""" |
|
|
category = user_data.get('category') |
|
|
date_filter = user_data.get('date_filter') |
|
|
price_min = user_data.get('price_min') |
|
|
price_max = user_data.get('price_max') |
|
|
zones = user_data.get('zones') |
|
|
country_code = user_data.get('country_code') |
|
|
|
|
|
filter_parts = [] |
|
|
filter_parts.append(f"Date: {date_filter.replace('_', ' ').title() if date_filter else 'Any'}") |
|
|
filter_parts.append(f"Category: {category if category else 'All'}") |
|
|
|
|
|
if price_min is not None or price_max is not None: |
|
|
price_text = "Price: " |
|
|
if price_min is not None: |
|
|
price_text += f"From {price_min} " |
|
|
if price_max is not None: |
|
|
price_text += f"To {price_max}" |
|
|
filter_parts.append(price_text) |
|
|
|
|
|
if zones: |
|
|
filter_parts.append(f"Zones: {', '.join(zones)}") |
|
|
|
|
|
if country_code: |
|
|
filter_parts.append(f"Country: {country_code}") |
|
|
|
|
|
return "Current Filters:\n" + "\n".join(filter_parts) |
|
|
|
|
|
async def handle_event_number_input(self, update: Update, context: ContextTypes.DEFAULT_TYPE): |
|
|
"""Handle the user's reply with an event number.""" |
|
|
|
|
|
if context.user_data.get('awaiting_event_number'): |
|
|
try: |
|
|
event_number_str = update.message.text.strip() |
|
|
event_number = int(event_number_str) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
current_events_on_page = context.user_data.get('current_event_list', []) |
|
|
current_skip = context.user_data.get('skip', 0) |
|
|
|
|
|
|
|
|
|
|
|
event_index_on_page = event_number - 1 - current_skip |
|
|
|
|
|
if 0 <= event_index_on_page < len(current_events_on_page): |
|
|
selected_event = current_events_on_page[event_index_on_page] |
|
|
|
|
|
|
|
|
await update.message.reply_text( |
|
|
f"You selected event number {event_number}:\n" |
|
|
f"๐ *{selected_event.get('title', 'N/A')}*\n" |
|
|
f"๐ Location: {selected_event.get('location', {}).get('title', 'N/A')}\n" |
|
|
f"What offer would you like to select for this event?" |
|
|
, parse_mode='Markdown' |
|
|
) |
|
|
|
|
|
context.user_data['awaiting_event_number'] = False |
|
|
context.user_data.pop('current_event_list', None) |
|
|
context.user_data.pop('skip', None) |
|
|
context.user_data.pop('total_events', None) |
|
|
|
|
|
|
|
|
else: |
|
|
|
|
|
await update.message.reply_text( |
|
|
f"Invalid event number '{event_number_str}'. Please reply with a number from the list above." |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
except ValueError: |
|
|
|
|
|
await update.message.reply_text( |
|
|
f"That doesn't look like a number. Please reply with the number of the event you want to select." |
|
|
|
|
|
) |
|
|
except Exception as e: |
|
|
logger.error(f"Error handling event number input: {e}") |
|
|
await update.message.reply_text("Sorry, there was an error processing your selection.") |
|
|
|
|
|
context.user_data['awaiting_event_number'] = False |
|
|
context.user_data.pop('current_event_list', None) |
|
|
context.user_data.pop('skip', None) |
|
|
context.user_data.pop('total_events', None) |
|
|
|
|
|
async def send_message(self, user_id: int, message: str): |
|
|
"""Helper method to send messages to users""" |
|
|
try: |
|
|
await self.application.bot.send_message( |
|
|
chat_id=user_id, |
|
|
text=message, |
|
|
parse_mode='Markdown', |
|
|
disable_web_page_preview=True |
|
|
) |
|
|
except Exception as e: |
|
|
logger.error(f"Error sending message to user {user_id}: {e}") |
|
|
|
|
|
async def _make_request(self, payload: dict): |
|
|
"""Make API request to WeBook""" |
|
|
response = requests.post(self.url, headers=self.headers, json=payload) |
|
|
response.raise_for_status() |
|
|
return response.json() |
|
|
|
|
|
async def start_bot(self): |
|
|
"""Start the bot asynchronously""" |
|
|
if self.is_running: |
|
|
return False, "Bot is already running" |
|
|
|
|
|
try: |
|
|
self.application = Application.builder().token(self.token).build() |
|
|
|
|
|
|
|
|
self.application.add_handler(CommandHandler("start", self.start_command)) |
|
|
self.application.add_handler(CommandHandler("setinfoaccountwebook", self.set_account_info_command)) |
|
|
self.application.add_handler(CommandHandler("stateloginwebook", self.account_status_command)) |
|
|
self.application.add_handler(CommandHandler("help", self.help_command)) |
|
|
self.application.add_handler(CommandHandler("info", self.info_command)) |
|
|
self.application.add_handler(CallbackQueryHandler(self.button_callback)) |
|
|
self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_credentials_input)) |
|
|
|
|
|
|
|
|
logger.info("Starting bot...") |
|
|
await self.application.initialize() |
|
|
await self.application.start() |
|
|
await self.application.run_polling() |
|
|
self.is_running = True |
|
|
return True, "Bot started successfully" |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error running bot: {str(e)}") |
|
|
self.is_running = False |
|
|
return False, f"Error starting bot: {str(e)}" |
|
|
|
|
|
async def stop_bot(self): |
|
|
"""Stop the bot""" |
|
|
if not self.is_running: |
|
|
return False, "Bot is not running" |
|
|
|
|
|
try: |
|
|
if self.application: |
|
|
await self.application.stop() |
|
|
await self.application.shutdown() |
|
|
self.is_running = False |
|
|
return True, "Bot stopped successfully" |
|
|
return False, "No application instance found" |
|
|
except Exception as e: |
|
|
logger.error(f"Error stopping bot: {str(e)}") |
|
|
return False, f"Error stopping bot: {str(e)}" |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN') |
|
|
if not BOT_TOKEN: |
|
|
raise ValueError("TELEGRAM_BOT_TOKEN not found in environment variables") |
|
|
|
|
|
bot = WeBookBot(BOT_TOKEN) |
|
|
asyncio.run(bot.start_bot()) |