Untitled
5 hours ago in Plain Text
import os
import logging
from telegram import Update, ReplyKeyboardMarkup, KeyboardButton
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
from firebase_admin import credentials, firestore, initialize_app
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Set up logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
# Initialize Firebase
try:
# Use environment variables for Firebase credentials
firebase_config = {
"type": "service_account",
"project_id": os.getenv('FIREBASE_PROJECT_ID'),
"private_key_id": os.getenv('FIREBASE_PRIVATE_KEY_ID'),
"private_key": os.getenv('FIREBASE_PRIVATE_KEY').replace('\\n', '\n') if os.getenv('FIREBASE_PRIVATE_KEY') else None,
"client_email": os.getenv('FIREBASE_CLIENT_EMAIL'),
"client_id": os.getenv('FIREBASE_CLIENT_ID'),
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": os.getenv('FIREBASE_CLIENT_CERT_URL'),
"universe_domain": "googleapis.com"
}
# Check if all required environment variables are set
required_vars = ['FIREBASE_PROJECT_ID', 'FIREBASE_PRIVATE_KEY', 'FIREBASE_CLIENT_EMAIL']
missing_vars = [var for var in required_vars if not os.getenv(var)]
if missing_vars:
raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")
cred = credentials.Certificate(firebase_config)
firebase_app = initialize_app(cred)
db = firestore.client()
logger.info("Firebase initialized successfully")
except Exception as e:
logger.error(f"Error initializing Firebase: {e}")
raise
# Telegram Bot Token
BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
if not BOT_TOKEN:
logger.error("TELEGRAM_BOT_TOKEN environment variable is not set")
raise ValueError("TELEGRAM_BOT_TOKEN environment variable is not set")
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /start is issued."""
try:
user = update.effective_user
args = context.args
logger.info(f"Start command received from user {user.id} with args: {args}")
if args:
verification_code = args[0]
logger.info(f"Verification code: {verification_code}")
await handle_verification(update, context, verification_code)
else:
# Ask for phone number if no verification code provided
keyboard = [[KeyboardButton("Share Phone Number", request_contact=True)]]
reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True, one_time_keyboard=True)
await update.message.reply_text(
"Welcome to Dopamine Quiz Bot! 👋\n\n"
"To verify your phone number, please share your contact using the button below.",
reply_markup=reply_markup
)
except Exception as e:
logger.error(f"Error in start command: {e}", exc_info=True)
await update.message.reply_text("An error occurred. Please try again.")
async def handle_verification(update: Update, context: ContextTypes.DEFAULT_TYPE, verification_code: str) -> None:
"""Handle verification code from the start command."""
try:
logger.info(f"Handling verification for code: {verification_code}")
# Look up the verification code in Firestore
verification_ref = db.collection('telegramVerifications').document(verification_code)
verification_doc = verification_ref.get()
logger.info(f"Verification document exists: {verification_doc.exists}")
if not verification_doc.exists:
await update.message.reply_text("Invalid verification code. Please try again from the website.")
return
verification_data = verification_doc.to_dict()
logger.info(f"Verification data: {verification_data}")
user_id = verification_data.get('userId')
phone_number = verification_data.get('phone')
if not user_id:
logger.error("No user ID found in verification data")
await update.message.reply_text("Invalid verification data. Please try again from the website.")
return
# Get the user document to check if it exists
user_ref = db.collection('users').document(user_id)
user_doc = user_ref.get()
logger.info(f"User document exists: {user_doc.exists}")
if not user_doc.exists:
await update.message.reply_text("User not found. Please try again from the website.")
return
# Update user document in Firestore to mark phone as verified
update_data = {
'phoneVerified': True,
'telegramUsername': update.effective_user.username,
'telegramId': update.effective_user.id
}
logger.info(f"Updating user document with: {update_data}")
await user_ref.update(update_data)
# Delete the verification code to prevent reuse
verification_ref.delete()
logger.info("Verification document deleted")
await update.message.reply_text(
"✅ Your phone number has been verified successfully!\n\n"
"You can now return to the website to continue."
)
logger.info(f"User {user_id} verified successfully via code {verification_code}")
except Exception as e:
logger.error(f"Error in handle_verification: {e}", exc_info=True)
await update.message.reply_text("An error occurred during verification. Please try again.")
async def handle_contact(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle the user's shared contact."""
try:
contact = update.message.contact
user_id = contact.user_id
logger.info(f"Contact shared by user {user_id}")
if user_id and user_id != update.effective_user.id:
await update.message.reply_text("Please share your own contact information.")
return
phone_number = contact.phone_number
if not phone_number:
await update.message.reply_text("No phone number found in contact.")
return
logger.info(f"Phone number received: {phone_number}")
# Format phone number to standard format (remove + if present for comparison)
formatted_phone = phone_number.lstrip('+')
logger.info(f"Formatted phone: {formatted_phone}")
# Check if this phone number exists in any pending verification
verifications_ref = db.collection('telegramVerifications')
query = verifications_ref.where('phone', '>=', formatted_phone).where('phone', '<=', formatted_phone + '\uf8ff').stream()
found = False
for doc in query:
verification_data = doc.to_dict()
logger.info(f"Found verification data: {verification_data}")
user_id = verification_data.get('userId')
if not user_id:
continue
# Update user document
user_ref = db.collection('users').document(user_id)
await user_ref.update({
'phoneVerified': True,
'telegramUsername': update.effective_user.username,
'telegramId': update.effective_user.id
})
# Delete the verification document
doc.reference.delete()
found = True
logger.info(f"User {user_id} verified successfully via contact sharing")
if found:
await update.message.reply_text(
"✅ Your phone number has been verified successfully!\n\n"
"You can now return to the website to continue."
)
else:
await update.message.reply_text(
"No pending verification found for this phone number. "
"Please start the verification process from the website first."
)
except Exception as e:
logger.error(f"Error in handle_contact: {e}", exc_info=True)
await update.message.reply_text("An error occurred while processing your contact. Please try again.")
async def debug_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Debug command to check bot status."""
try:
user = update.effective_user
message = (
f"🤖 *Bot Debug Information*\n\n"
f"*User ID:* {user.id}\n"
f"*Username:* {user.username}\n"
f"*First Name:* {user.first_name}\n"
f"*Last Name:* {user.last_name}\n\n"
f"Bot is running correctly. Use /start with your verification code to begin."
)
await update.message.reply_text(message, parse_mode='Markdown')
except Exception as e:
logger.error(f"Error in debug_command: {e}")
await update.message.reply_text("Error generating debug information.")
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /help is issued."""
try:
await update.message.reply_text(
"🤖 *Dopamine Quiz Bot Help*\n\n"
"This bot helps verify your phone number for the Dopamine Quiz website.\n\n"
"*How to use:*\n"
"1. Start the verification process on the website\n"
"2. Click the 'Verify via Telegram' button\n"
"3. This bot will automatically verify your phone number\n\n"
"If you have any issues, please contact support.\n\n"
"Use /debug to check bot status.",
parse_mode='Markdown'
)
except Exception as e:
logger.error(f"Error in help_command: {e}")
async def status_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Check verification status."""
try:
# Check if user exists in our database
user_id = update.effective_user.id
users_ref = db.collection('users')
query = users_ref.where('telegramId', '==', user_id).stream()
verified = False
for doc in query:
user_data = doc.to_dict()
if user_data.get('phoneVerified'):
verified = True
break
if verified:
await update.message.reply_text(
"✅ Your phone number is already verified!\n\n"
"You can return to the website to continue."
)
else:
await update.message.reply_text(
"Your phone number is not yet verified.\n\n"
"Please start the verification process from the website first."
)
except Exception as e:
logger.error(f"Error in status_command: {e}")
await update.message.reply_text("An error occurred while checking your status. Please try again.")
async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Log errors caused by Updates."""
logger.error(f"Exception while handling an update: {context.error}", exc_info=context.error)
def main() -> None:
"""Start the bot."""
# Create the Application and pass it your bot's token.
application = Application.builder().token(BOT_TOKEN).build()
# Add handlers
application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("help", help_command))
application.add_handler(CommandHandler("status", status_command))
application.add_handler(CommandHandler("debug", debug_command))
application.add_handler(MessageHandler(filters.CONTACT, handle_contact))
# Add error handler
application.add_error_handler(error_handler)
# Run the bot until the user presses Ctrl-C
logger.info("Bot started successfully")
application.run_polling()
if __name__ == "__main__":
main()