diff --git a/README.md b/README.md index b70a061..ed0d550 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ python main.py The bot listens for commands: - `/op ` — parse an opportunity - `/ev ` — parse an event +- If you send a URL directly in chat, the bot will ask whether to process it as an event or an opportunity using buttons. If you paste text (instead of sending a URL), the bot will parse it and when you click Save it will prompt you for a source URL (or you can `/skip`). diff --git a/__pycache__/bot.cpython-314.pyc b/__pycache__/bot.cpython-314.pyc new file mode 100644 index 0000000..17ade3b Binary files /dev/null and b/__pycache__/bot.cpython-314.pyc differ diff --git a/bot.py b/bot.py index f8ed51c..10dd58b 100644 --- a/bot.py +++ b/bot.py @@ -3,6 +3,7 @@ import asyncio import sys import concurrent.futures import logging +import re from contextlib import suppress from dotenv import load_dotenv @@ -94,6 +95,17 @@ def retry(max_attempts=3, backoff_factor=2, initial_delay=1): task_queue = asyncio.Queue() +def is_http_url(text: str) -> bool: + return bool(re.match(r'^https?://\S+$', text.strip())) + + +def build_url_choice_keyboard(url: str): + return InlineKeyboardMarkup([ + [InlineKeyboardButton("📅 Process as Event", callback_data='choose_type:event')], + [InlineKeyboardButton("📋 Process as Opportunity", callback_data='choose_type:opportunity')], + ]) + + def build_entry_summary(data, entry_type, saved=False): if entry_type == "event": event_datetime = data.get('date_time') or data.get('datetime') @@ -182,7 +194,8 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): "Welcome! I can extract arts opportunities and events.\n\n" "📋 **Commands:**\n" "/op - Extract an opportunity\n" - "/ev - Extract an event" + "/ev - Extract an event\n\n" + "You can also send a URL directly and I will ask whether to process it as an event or opportunity." ) async def handle_opportunity(update: Update, context: ContextTypes.DEFAULT_TYPE): @@ -230,6 +243,15 @@ async def handle_followup_text(update: Update, context: ContextTypes.DEFAULT_TYP return if not context.user_data.get('awaiting_save_url'): + text = (update.message.text or '').strip() + if not text or not is_http_url(text): + return + + context.user_data['pending_url_to_process'] = text + await update.message.reply_text( + "What should I do with this URL?", + reply_markup=build_url_choice_keyboard(text) + ) return text = update.message.text.strip() @@ -257,6 +279,18 @@ async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query await query.answer() + if query.data.startswith('choose_type:'): + pending_url = context.user_data.get('pending_url_to_process') + if not pending_url: + await query.edit_message_text("❌ I couldn't find a pending URL to process.") + return + + entry_type = query.data.split(':', 1)[1] + context.user_data['pending_url_to_process'] = None + await query.edit_message_text(f"📥 Queued URL for {entry_type} processing...") + await task_queue.put((update, context, pending_url, entry_type, 'url')) + return + if query.data == 'save_db': data = context.user_data.get('last_extracted') entry_type = context.user_data.get('last_entry_type', 'opportunity')