Quizbot, or questionnaire bot for Telegram. Using python, aiogram.
16.11.2023
19.12.2024
11 minutes
1418
4
0
0
0
Introduction
Hello, in this case I will show you how, I made my quiz bot.
I will provide the source code of the python and bash scripts. I will explain the structure and purpose of each file
and the handler. And of course you can interact with my ready-made bot.
-
You can find him by:
- Link https://t.me/TimQuizzerBot
- Or typing in telegram TimQuizzer
Alas, when I made it, I did not intend it to be multilingual. And when I realized it, I didn’t dare to redo everything. Yes and
The essence of this bot is not multilingual. But next time, I will definitely make a multilingual bot. So it will be easier for all non-Russian speakers.
An example of creating a Telegram survey bot.
Quiz bot structure
My bot has a linear/sequential structure. With some 'floating' handlers.
What do I mean by this?
Since I have a survey bot. And he must ask questions sequentially, one by one.
Then the handlers that process user responses must be called sequentially, one after another.
By 'floating' handlers, I meant those functions that can be called at any time during communication with the bot.
-
There are 5 of them.
- /start ➩ The bot starts. Welcome message.
- /help ➩ Shows available commands
- /menu ➩ Shows available commands
- /settings ➩ Configure result output
- /result ➩ Displays the result of people's surveys
-
Let's look at the structure of the survey bot project.
.env ➩ File containing the bot token. When downloading from the repository, it will not be there. You need to do it yourself. Take a look at README.md file
data.json ➩ Stores data about all users. Database. Yes, without MySQL and other SQL for now. Created automatically.
result_template.md ➩ Template that is used to display information.
requirements.txt ➩ Necessary packages for working in a virtual environment
main.py ➩ Entry point. Contains all request handlers
config.py ➩ Configuration file. Contains global variables.
formaters.py ➩ Functions, the essence of which is to be able to change the output of the results in the message.
placers.py ➩ Functions that interact with the template. Insert data into it.
utils.py ➩ Various utilities.
/start command

@bot_dispatcher.message(CommandStart())
async def command_start_handler(message: Message) -> None:
builder = InlineKeyboardBuilder()
for menu_item in menu:
builder.button(text=menu_item, callback_data="menu_"+menu_item)
builder.adjust(2)
await message.answer(f"Привет, *{message.from_user.first_name}*\!\nЯ бот опросник и я могу _провести опрос_ и _показать общие результаты_ опросов других\. \nТакже ты можешь _настроить тип и формат_ выводимой информации\.", reply_markup=builder.as_markup(), parse_mode=ParseMode.MARKDOWN_V2)
Age of respondent

@bot_dispatcher.callback_query(F.data == "menu_"+menu[0])
async def your_age_handler(callback: types.CallbackQuery) -> None:
builder = ReplyKeyboardBuilder()
for age in ages:
builder.add(types.KeyboardButton(text=age))
builder.adjust(1)
await callback.message.answer("Твой возраст? ", reply_markup=builder.as_markup(one_time_keyboard=True))
Country of interviewee

@bot_dispatcher.message(F.text.startswith('от') | F.text.contains('до'))
async def your_country_handler(message: Message) -> None:
user_values = {
"user_id": message.from_user.id,
"age": message.text,
"country": None,
"sex": None,
"work": None,
"car": None,
"is_complete": False,
}
with open(f"user_{message.from_user.id}.json", "w", encoding="utf-8") as file:
json.dump(user_values, file)
builder = ReplyKeyboardBuilder()
for contry in countries:
builder.add(types.KeyboardButton(text=f"|{contry['emoji']} {contry['name']}|"))
builder.adjust(5)
await message.answer("Твоя страна? ", reply_markup=builder.as_markup(one_time_keyboard=True))
Gender of respondent

@bot_dispatcher.message(F.text.startswith('|') | F.text.endswith('|'))
async def your_sex_handler(message: Message) -> None:
start_pos = message.text.find(' ') + 1
await update_user(message.from_user.id, 'country', message.text[start_pos:])
builder = ReplyKeyboardBuilder()
builder.add(types.KeyboardButton(text="♂ Мужчина"))
builder.add(types.KeyboardButton(text="♀ Женщина"))
await message.answer("Твой пол? ", reply_markup=builder.as_markup(one_time_keyboard=True))
Interviewee's work

@bot_dispatcher.message(F.text.startswith('♂') | F.text.startswith('♀'))
async def your_work_handler(message: Message) -> None:
start_pos = message.text.find(' ') + 1
await update_user(message.from_user.id, 'sex', message.text[start_pos:])
builder = InlineKeyboardBuilder()
for work in works:
builder.button(text=work, callback_data="work_" + work)
builder.adjust(3)
await message.answer(
"Где работаешь, то есть, какая сфера ?",
reply_markup=builder.as_markup(one_time_keyboard=True)
)
Interviewee's car

@bot_dispatcher.callback_query(F.data.startswith("work_"))
async def your_car_handler(callback: types.CallbackQuery) -> None:
start_pos = callback.data.find('_') + 1
await update_user(callback.from_user.id, 'work', callback.data[start_pos:])
builder = ReplyKeyboardBuilder()
for car in cars:
builder.add(types.KeyboardButton(text=car))
await callback.message.answer("Есть ли у тебя машина ?", reply_markup=builder.as_markup(one_time_keyboard=True))
End of survey

@bot_dispatcher.message(F.text.contains("машин"))
async def end_quiz_handler(message: Message) -> None:
await update_user(message.from_user.id, 'car', message.text)
if await is_user_completed(message.from_user.id):
with open(f"user_{message.from_user.id}.json", "r", encoding="utf-8") as file:
user_values = json.load(file)
if not os.path.exists(FILE):
with open(FILE, "a", encoding="utf-8") as file:
file.write("[]")
with open(FILE, "r", encoding="utf-8") as file:
users = json.load(file)
users.append(user_values)
with open(FILE, "w", encoding="utf-8") as file:
json.dump(users, file)
os.remove(f"user_{message.from_user.id}.json")
builder = InlineKeyboardBuilder()
builder.button(text="Результат", callback_data="menu_" + menu[1])
await message.answer("Опрос закончен.\nСпасибо за участие.")
await message.answer("Теперь можно посмотреть на результат.", reply_markup=builder.as_markup())
Main Menu

@bot_dispatcher.message(Command('help', 'menu'))
async def menu_handler(message: Message) -> None:
builder = InlineKeyboardBuilder()
for menu_item in menu:
builder.button(text=menu_item, callback_data="menu_"+menu_item)
builder.adjust(2)
await message.answer(
"""
Комманды для ручного ввода:
\t*/help* или */menu* ➩ Это меню\.
\t*/start* ➩ Общее меню опроса\.
\t*/result* ➩ Результаты опроса участников\.
\t*/settings* ➩ Настройки опросника\.
""",
parse_mode=ParseMode.MARKDOWN_V2)
await message.answer("Или как InlineButtons", reply_markup=builder.as_markup())
Settings

This handler is divided into 4 functions. One, *setting_update_format_numbers*,
directly implements the functionality of changing the format of the displayed information.
@bot_dispatcher.callback_query(F.data.contains("format_setting_"))
async def setting_update_format_numbers(callback: types.CallbackQuery):
builder = InlineKeyboardBuilder()
for set_menu in format_settings_menu:
if set_menu in callback.data:
builder.button(text="+ " + set_menu, callback_data="format_setting_"+set_menu)
global division_type
division_type = set_menu
else:
builder.button(text=set_menu, callback_data="format_setting_"+set_menu)
builder.adjust(2)
await callback.bot.edit_message_reply_markup(chat_id=callback.message.chat.id, message_id=callback.message.message_id, reply_markup=builder.as_markup())
The other one, *setting*, shows possible settings, creates the corresponding buttons.
async def setting(message: Message) -> None:
builder = InlineKeyboardBuilder()
for set_menu in format_settings_menu:
builder.button(text=set_menu, callback_data="format_setting_"+set_menu)
builder.adjust(2)
await message.answer("Настройки формата", reply_markup=builder.as_markup())
And the other two are implemented via *callback_query* and *message*. And they both cause
*setting*. This was done in order to be able to work with this function,
both with a command and with a button.
@bot_dispatcher.message(Command("settings"))
async def setting_command_handler(message: Message) -> None:
await setting(message)
@bot_dispatcher.callback_query(F.data == "menu_"+menu[2])
async def setting_callback_handler(callback: types.CallbackQuery):
await setting(callback.message)
Result

@bot_dispatcher.message(Command("result"))
async def result_command_handler(message: Message) -> None:
await result(message)
@bot_dispatcher.callback_query(F.data == "menu_"+menu[1])
async def result_callback_handler(callback: types.CallbackQuery) -> None:
await result(callback.message)
The *result* handler itself is responsible for formatting result_template.md.
Inserts data using functions from *placers.py*. And the format is changed using *formaters.py*
async def result(message: Message) -> None:
if not os.path.exists(FILE):
builder = InlineKeyboardBuilder()
builder.button(text="Начать", callback_data="menu_" + menu[0])
await message.answer("Извини, база данных пуста. Пройди опрос первым !", reply_markup=builder.as_markup())
return
with open(FILE, "r", encoding="utf-8") as file:
users = json.load(file)
with open("result_template.md", "r", encoding="utf-8") as file:
template = file.read()
# Define result output
result = template.replace("divisiontype", division_type)
# Result output in absolute numbers
if division_type == format_settings_menu[0]:
result = place_age(result, users, in_absolute)
result = place_country(result, users, in_absolute)
result = place_sex(result, users, in_absolute)
result = place_work(result, users, in_absolute)
result = place_car(result, users, in_absolute)
else:
result = place_age(result, users, in_percent)
result = place_country(result, users, in_percent)
result = place_sex(result, users, in_percent)
result = place_work(result, users, in_percent)
result = place_car(result, users, in_percent)
await message.answer(result, parse_mode=ParseMode.MARKDOWN_V2)
Download a ready-made telegram bot, or copy the code
You can download the bot from my repository.
TimQuizzer-bot
Or you can view the source code here and copy the parts you are interested in
main.py
config.py
formaters.py
placers.py
utils.py
result_template.md
Conclusion
This is how I wrote my quiz bot. Perhaps it will seem simple to you, and you will be entirely right.
But you need to publish articles, right? In any case, I hope this case can help you in writing
own survey bot.
Comments
(0)
Send
It's empty now. Be the first (o゚v゚)ノ
Other
Used termins
- Telegram bot ⟶ This is a program that, using the Telegram **API**, can perform various actions in chats without a person.
- aiogram ⟶ It is a framework that build upon asyncio and aiohttp python modules, fully asynchronous, for creating telegram bots.
Related questions
- What is the difference between chat and channel in Telegram? There is a difference between a chat and a channel, and they are quite significant. In a channel, there is one-way communication between the creator and the audience, in chats, everyone communicates as equals. In all other respects, chats are lighter than channels. So there is no audience limit in channels, in chats up to 200 thousand.
- Why doesn't my bot see messages from other bots? Bots talking to each other could potentially get stuck in unwelcome loops. To avoid this, we decided that bots will not be able to see messages from other bots regardless of mode.
- What is a telegram bot for? Telegram bots can be used for various reasons. They are universal assistants in business, can provide a convenient format for interaction with clients or be an excellent platform for hosting a website or tool.