Repository navigation
Expand file tree
/
Copy pathquizz.py
More file actions
418 lines (344 loc) Β· 17.8 KB
/
Copy pathquizz.py
File metadata and controls
418 lines (344 loc) Β· 17.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
import asyncio
import logging
import random
import time # Import time module to track timing
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup
from telegram.constants import ParseMode, ChatAction
from telegram.ext import Application, CommandHandler, MessageHandler, CallbackContext, ConversationHandler, filters, CallbackQueryHandler
# Bot token
bot_token = '7518438812:AAF29rspjnbm48FQMZXJBCTOL1U5HOUJC-4'
# Quiz states
(ASK_NAME, ASK_QUESTION_TYPE, ASK_QUESTION, ASK_OPTIONS, ASK_ANSWER, ASK_TIME_GAP, ADD_MORE_QUESTIONS) = range(7)
def main():
port = int(os.environ.get('PORT', 8080)) # Fetch port from environment variable
# Global variables for managing quizzes and participants
quizzes = {} # Stores quizzes
quiz_participants = {} # Track participants' answers per quiz
user_quizzes = {} # Store quizzes created by users
active_question = {} # Stores the current active question for each quiz
question_results = {} # Track question-specific results for all questions
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# --- START COMMAND AND MENU ---
async def start(update: Update, context: CallbackContext):
user = update.message.from_user
await context.bot.send_chat_action(chat_id=user.id, action=ChatAction.TYPING)
await asyncio.sleep(1)
keyboard = [
[KeyboardButton("/createquiz"), KeyboardButton("/myquizzes")],
[KeyboardButton("/startquiz"), KeyboardButton("/pausequiz")],
[KeyboardButton("/resumequiz"), KeyboardButton("/deletequiz")],
[KeyboardButton("/results"), KeyboardButton("/random")],
[KeyboardButton("/allcommands")]
]
reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
await context.bot.send_message(chat_id=user.id,
text=f"π Hello, *{user.first_name}*! Welcome to the Quiz Bot! π€",
parse_mode=ParseMode.MARKDOWN,
reply_markup=reply_markup)
# --- ALL COMMANDS FUNCTION ---
async def all_commands(update: Update, context: CallbackContext):
commands = (
"/start - Start the bot\n"
"/createquiz - Create a new quiz\n"
"/myquizzes - View quizzes youβve created\n"
"/startquiz <quiz_name> - Start a quiz\n"
"/deletequiz <quiz_name> - Delete a quiz\n"
"/pausequiz <quiz_name> - Pause a quiz\n"
"/resumequiz <quiz_name> - Resume a paused quiz\n"
"/results <quiz_name> - View quiz results\n"
"/random - Choose a random member from the group\n"
"/allcommands - Show all available commands"
)
await update.message.reply_text(f"π Here are all the available commands:\n\n{commands}")
# --- MY QUIZZES FUNCTION ---
async def my_quizzes(update: Update, context: CallbackContext):
user_id = update.message.from_user.id
user_quiz_list = user_quizzes.get(user_id, [])
if user_quiz_list:
await update.message.reply_text(f"π Here are your quizzes:\n" + "\n".join(user_quiz_list))
else:
await update.message.reply_text("β You haven't created any quizzes yet.")
# --- QUIZ CREATION HANDLERS ---
async def create_quiz(update: Update, context: CallbackContext):
await update.message.reply_text("π Please enter a name for your quiz:")
return ASK_NAME
async def ask_name(update: Update, context: CallbackContext):
quiz_name = update.message.text
user_id = update.message.from_user.id
context.user_data['quiz_name'] = quiz_name
context.user_data['questions'] = []
context.user_data['creator'] = user_id
if user_id not in user_quizzes:
user_quizzes[user_id] = []
user_quizzes[user_id].append(quiz_name)
await update.message.reply_text("β³ How many seconds should the gap between each question be?")
return ASK_TIME_GAP
async def ask_time_gap(update: Update, context: CallbackContext):
try:
if update.message.text.isdigit():
time_gap = int(update.message.text)
context.user_data['time_gap'] = time_gap
keyboard = [[InlineKeyboardButton("Single Question", callback_data='single')],
[InlineKeyboardButton("Multiple Questions", callback_data='multiple')]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text("π Do you want to create a 'Single Question' or 'Multiple Questions' quiz?",
reply_markup=reply_markup)
return ASK_QUESTION_TYPE
else:
await update.message.reply_text("β Invalid input. Please enter a valid number of seconds.")
return ASK_TIME_GAP
except Exception as e:
logger.error(f"Error in ask_time_gap handler: {e}")
await update.message.reply_text("An error occurred. Please try again.")
return ASK_TIME_GAP
async def ask_question_type(update: Update, context: CallbackContext):
query = update.callback_query
await query.answer()
answer = query.data
if answer == 'single':
context.user_data['quiz_type'] = 'single'
await query.edit_message_text("π― You've chosen to create a 'Single Question' quiz.\nPlease send the question.")
return ASK_QUESTION
elif answer == 'multiple':
context.user_data['quiz_type'] = 'multiple'
await query.edit_message_text("π’ You've chosen to create a 'Multiple Questions' quiz.\nPlease send the first question.")
return ASK_QUESTION
# --- QUESTION AND ANSWER HANDLERS ---
async def ask_question(update: Update, context: CallbackContext):
context.user_data['current_question'] = update.message.text
await update.message.reply_text("π€ Got it! Now, send the options for this question (comma-separated).")
return ASK_OPTIONS
async def ask_options(update: Update, context: CallbackContext):
options = update.message.text.split(',')
context.user_data['current_options'] = options
await update.message.reply_text("β
Now, please send the correct answer number (e.g., 1 for the first option).")
return ASK_ANSWER
async def ask_answer(update: Update, context: CallbackContext):
try:
if update.message.text.isdigit():
correct_answer = int(update.message.text) - 1 # Correct answer in 0-based index
context.user_data['correct_answer'] = correct_answer
context.user_data['questions'].append({
'question': context.user_data['current_question'],
'options': context.user_data['current_options'],
'correct_answer': context.user_data['correct_answer']
})
if context.user_data['quiz_type'] == 'multiple':
keyboard = [[InlineKeyboardButton("β Add More Questions", callback_data='add_more')],
[InlineKeyboardButton("β Complete Quiz", callback_data='complete_quiz')]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text("Do you want to add more questions or complete the quiz?",
reply_markup=reply_markup)
return ADD_MORE_QUESTIONS
else:
await finalize_quiz(update, context)
return ConversationHandler.END
else:
await update.message.reply_text("β Invalid input. Please enter a number for the correct answer.")
return ASK_ANSWER
except Exception as e:
logger.error(f"Error in ask_answer handler: {e}")
await update.message.reply_text("An error occurred. Please try again.")
return ASK_ANSWER
# --- ADD MORE QUESTIONS HANDLER ---
async def add_more_questions(update: Update, context: CallbackContext):
query = update.callback_query
await query.answer()
if query.data == 'add_more':
await query.edit_message_text("π’ Please send the next question.")
return ASK_QUESTION
elif query.data == 'complete_quiz':
await finalize_quiz(update, context)
await query.edit_message_text("β You have completed the quiz! You can start it now.")
return ConversationHandler.END
# --- FINALIZE THE QUIZ ---
async def finalize_quiz(update: Update, context: CallbackContext):
user_id = context.user_data['creator']
quiz_name = context.user_data['quiz_name']
questions = context.user_data['questions']
time_gap = context.user_data.get('time_gap', 0)
quizzes[quiz_name] = {
'creator': user_id,
'questions': questions,
'status': 'ready',
'time_gap': time_gap
}
await update.message.reply_text(f"π You have completed the quiz! You can start it with /startquiz {quiz_name}.")
return ConversationHandler.END
# --- START QUIZ WITH SIMPLE MESSAGE QUESTIONS ---
async def start_quiz(update: Update, context: CallbackContext):
args = context.args
user_id = update.message.from_user.id
if not args:
await update.message.reply_text("β You need to provide a quiz name. Usage: /startquiz <quiz_name>")
return
quiz_name = ' '.join(args)
if quiz_name not in quizzes:
await update.message.reply_text("β Quiz not found. Please provide a valid quiz name.")
return
quiz = quizzes[quiz_name]
if user_id != quiz['creator']:
await update.message.reply_text("β Only the quiz creator can start the quiz.")
return
await update.message.reply_text(f"π Starting quiz '{quiz_name}'!")
# Reset participants for the entire quiz
if quiz_name not in question_results:
question_results[quiz_name] = {}
# Send the questions one by one as a message and handle answers with time gap
for idx, question_data in enumerate(quiz['questions']):
active_question[quiz_name] = {
'question_index': idx,
'correct_answer': question_data['correct_answer'],
'start_time': time.time(),
'participants': {} # Track participants for each question
}
# Send the question as a simple message
question_text = f"β *Question {idx + 1}:* {question_data['question']}\n" + \
"\n".join([f"{i + 1}. {opt}" for i, opt in enumerate(question_data['options'])])
await context.bot.send_message(
chat_id=update.effective_chat.id,
text=question_text,
parse_mode=ParseMode.MARKDOWN
)
# Wait for the time gap before the next question
time_gap = quiz.get('time_gap', 0)
if time_gap > 0 and idx < len(quiz['questions']) - 1:
await asyncio.sleep(time_gap)
# After sending all questions, automatically send results to the creator
await send_results_to_creator(update, context, quiz_name)
# --- HANDLE TEXT ANSWERS ---
async def handle_text_answer(update: Update, context: CallbackContext):
if not update.message or not update.message.text.isdigit(): # Ensure only numeric answers are handled
return
user = update.message.from_user
user_id = user.id
answer_text = update.message.text.strip()
quiz_name = None
# Find the active quiz
for qname, data in active_question.items():
if data:
quiz_name = qname
break
if not quiz_name:
return # Ignore if there's no active question
quiz_data = active_question[quiz_name]
participants = quiz_data['participants']
question_index = quiz_data['question_index']
# Check if the user has already answered
if user_id in participants:
return # Ignore further answers from the same user for this question
# Validate if the answer is within the options
answer = int(answer_text) - 1 # Convert to 0-based index
correct_answer = quiz_data['correct_answer']
# Check if the answer is correct
if answer == correct_answer:
time_taken = time.time() - quiz_data['start_time']
participants[user_id] = {'correct': True, 'time_taken': time_taken}
else:
participants[user_id] = {'correct': False, 'time_taken': None}
# Store the participant's result for this question in question_results
if quiz_name not in question_results:
question_results[quiz_name] = {}
if question_index not in question_results[quiz_name]:
question_results[quiz_name][question_index] = {}
question_results[quiz_name][question_index][user_id] = {
'correct': answer == correct_answer,
'time_taken': time_taken if answer == correct_answer else None
}
# --- SEND RESULTS TO CREATOR ---
async def send_results_to_creator(update: Update, context: CallbackContext, quiz_name: str):
quiz = quizzes.get(quiz_name)
creator_id = quiz['creator']
if quiz_name not in question_results or not question_results[quiz_name]:
await context.bot.send_message(chat_id=creator_id, text="No participants answered any questions.")
return
# Prepare the results message per question
result_message = f"π *Results for Quiz '{quiz_name}':*\n\n"
for question_index, participants in question_results[quiz_name].items():
result_message += f"**Results for Question {question_index + 1}:**\n"
sorted_participants = sorted(participants.items(), key=lambda x: (x[1]['correct'], -x[1]['time_taken']), reverse=True)
for i, (user_id, result) in enumerate(sorted_participants, start=1):
user = await context.bot.get_chat(user_id)
result_message += f"{i}. {user.username or user.first_name}: {'Correct' if result['correct'] else 'Incorrect'} "
if result['correct']:
result_message += f"(answered in {result['time_taken']:.2f} seconds)\n"
else:
result_message += "\n"
# Summarize the top 3 participants with the most correct answers
correct_answers_summary = {}
for participants in question_results[quiz_name].values():
for user_id, result in participants.items():
if result['correct']:
correct_answers_summary[user_id] = correct_answers_summary.get(user_id, 0) + 1
sorted_summary = sorted(correct_answers_summary.items(), key=lambda x: x[1], reverse=True)
result_message += "\nπ **Overall Quiz Winners:**\n"
for i, (user_id, correct_answers) in enumerate(sorted_summary[:3], start=1):
user = await context.bot.get_chat(user_id)
result_message += f"{i}. {user.username or user.first_name}: {correct_answers} correct answers\n"
# Escape special characters in result_message for Markdown
result_message = result_message.replace("_", "\\_").replace("*", "\\*")
# Send the results privately to the creator
try:
await context.bot.send_message(chat_id=creator_id, text=result_message, parse_mode=ParseMode.MARKDOWN)
except Exception as e:
logger.error(f"Error sending results to creator: {e}")
await context.bot.send_message(chat_id=creator_id, text="β An error occurred while sending the results.")
# --- /RESULTS COMMAND TO RETRIEVE RESULTS ---
async def end_quiz(update: Update, context: CallbackContext):
args = context.args
if not args:
await update.message.reply_text("β Please provide the quiz name. Usage: /results <quiz_name>")
return
quiz_name = ' '.join(args)
if quiz_name not in quizzes:
await update.message.reply_text("β Quiz not found. Please provide a valid quiz name.")
return
# Send results to the quiz creator using the send_results_to_creator function
await send_results_to_creator(update, context, quiz_name)
# --- DELETE QUIZ FUNCTION ---
async def delete_quiz(update: Update, context: CallbackContext):
args = context.args
user_id = update.message.from_user.id
if not args:
await update.message.reply_text("β You need to provide a quiz name. Usage: /deletequiz <quiz_name>")
return
quiz_name = ' '.join(args)
if quiz_name not in quizzes:
await update.message.reply_text("β Quiz not found. Please provide a valid quiz name.")
return
quiz = quizzes[quiz_name]
if user_id != quiz['creator']:
await update.message.reply_text("β Only the quiz creator can delete the quiz.")
return
del quizzes[quiz_name]
await update.message.reply_text(f"π The quiz '{quiz_name}' has been deleted.")
# --- MAIN FUNCTION ---
def main():
application = Application.builder().token(bot_token).build()
conv_handler = ConversationHandler(
entry_points=[CommandHandler("createquiz", create_quiz)],
states={
ASK_NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, ask_name)],
ASK_QUESTION_TYPE: [CallbackQueryHandler(ask_question_type)],
ASK_QUESTION: [MessageHandler(filters.TEXT & ~filters.COMMAND, ask_question)],
ASK_OPTIONS: [MessageHandler(filters.TEXT & ~filters.COMMAND, ask_options)],
ASK_ANSWER: [MessageHandler(filters.TEXT & ~filters.COMMAND, ask_answer)],
ASK_TIME_GAP: [MessageHandler(filters.TEXT & ~filters.COMMAND, ask_time_gap)],
ADD_MORE_QUESTIONS: [CallbackQueryHandler(add_more_questions)]
},
fallbacks=[CommandHandler("cancel", lambda update, context: update.message.reply_text("Quiz creation canceled."))]
)
application.add_handler(conv_handler)
application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("myquizzes", my_quizzes))
application.add_handler(CommandHandler("allcommands", all_commands))
application.add_handler(CommandHandler("startquiz", start_quiz))
application.add_handler(CommandHandler("deletequiz", delete_quiz))
application.add_handler(CommandHandler("deletequiz", delete_quiz))
application.add_handler(CommandHandler("results", end_quiz))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text_answer))
application.run_polling()
if __name__ == '__main__':
main()