3 horizontal lines, burger
3 horizontal lines, burger
3 horizontal lines, burger
3 horizontal lines, burger

3 horizontal lines, burger
Remove all
LOADING ...

Content



    Quizbot, or questionnaire bot for Telegram. Using python, aiogram.

    Clock
    16.11.2023
    /
    Clock
    05.10.2025
    /
    Clock
    11 minutes
    An eye
    11079
    Hearts
    4
    Connected dots
    0
    Connected dots
    2
    Connected dots
    0
    Tags:
    Bot

    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.
    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.
    • txt file.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
    • js filedata.json ➩ Stores data about all users. Database. Yes, without MySQL and other SQL for now. Created automatically.
    • markdown fileresult_template.md ➩ Template that is used to display information.
    • txt filerequirements.txt ➩ Necessary packages for working in a virtual environment
    • python filemain.py ➩ Entry point. Contains all request handlers
    • python fileconfig.py ➩ Configuration file. Contains global variables.
    • python fileformaters.py ➩ Functions, the essence of which is to be able to change the output of the results in the message.
    • python fileplacers.py ➩ Functions that interact with the template. Insert data into it.
    • python fileutils.py ➩ Various utilities.

    /start command

    Welcomes new arrivals. Creates buttons in a message that invites you to take a survey.
    @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

    Asks for age and creates keyboard buttons.
    @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

    Creates a dictionary user_values, and immediately assigns it the age and user id.
    Creates buttons on the keyboard with country emojis on them. And of course he asks the country of the person being interviewed.
    @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

    Catches the text of the selected country and adds it to user_values.
    Asks for gender, and creates buttons on the keyboard with stubs ♂ and ♀
    @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

    Processes text from the previous handler, your_sex_handler, adding gender to the user.
    Asks about work, creates buttons in the message.
    @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

    Stores the user's job value in user_values
    He asks if there is a car?
    Creates buttons on the keyboard
    @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

    Adds information about the machine to the user.
    After which, it saves the user to the data.json database. Unless of course the user polled completely, rather than accidentally calling a handler in the middle.
    It might be worth adding a check for duplicate users. But I didn't do that because I wanted to give users the opportunity to participate several times.
    Displays a standard message and a button in the message with the ability to show the result.
    @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

    This handler can be called via the commands */help* or */menu*.
    He doesn't do anything special. Create buttons in the message. We display a standard message.
    @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

    There are two handlers. One for processing the "/result" command, the other for calling through buttons. Both have the same functionality *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

    import asyncio import logging import sys import json import os from aiogram import F from aiogram import Bot, Dispatcher, types from aiogram.enums import ParseMode from aiogram.filters import CommandStart, Command from aiogram.types import Message from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder from config import TOKEN, FILE, menu, division_type, format_settings_menu, cars, ages, sexes, works, countries from utils import update_user, is_user_completed from formaters import in_absolute, in_percent from placers import place_age, place_car, place_sex, place_work, place_country bot_dispatcher = Dispatcher() @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()) @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) @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)) @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)) @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)) @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) ) @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)) @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()) @bot_dispatcher.callback_query(F.data == "menu_"+menu[2]) async def setting_callback_handler(callback: types.CallbackQuery): await setting(callback.message) @bot_dispatcher.message(Command("settings")) async def setting_command_handler(message: Message) -> None: await setting(message) @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()) @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) 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()) 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) async def main() -> None: bot = Bot(TOKEN, parse_mode=ParseMode.HTML) await bot_dispatcher.start_polling(bot) if __name__ == "__main__": logging.basicConfig(level=logging.INFO, stream=sys.stdout) asyncio.run(main())

    config.py

    with open(".env", "r") as file: buffer = file.read() line_pos = buffer.find("BOT_TOKEN") TOKEN = buffer[buffer.find("=", line_pos) + 1:buffer.find("\n", line_pos)] FILE = "data.json" # Supported countries countries = [ {'emoji': '🇺🇸','name': 'США'}, {'emoji': '🇷🇺','name': 'Россия'}, {'emoji': '🇵🇱','name': 'Польша'}, {'emoji': '🇨🇳','name': 'Китай'}, {'emoji': '🇦🇽','name': 'Швеция'}, {'emoji': '🇦🇲','name': 'Армения'}, {'emoji': '🇨🇿','name': 'Чехия'}, {'emoji': '🇩🇰','name': 'Дания'}, {'emoji': '🇯🇴','name': 'Палестина'}, {'emoji': '🇪🇪','name': 'Эстония'}, {'emoji': '🇪🇬','name': 'Египет'}, {'emoji': '🇧🇾','name': 'Беларусь'}, {'emoji': '🇧🇷','name': 'Бразилия'}, {'emoji': '🇨🇦','name': 'Канада'}, {'emoji': '🇫🇮','name': 'Финляндия'}, {'emoji': '🇫🇷','name': 'Франция'}, {'emoji': '🇬🇷','name': 'Греция'}, {'emoji': '🇩🇪','name': 'Германия'}, {'emoji': '🇬🇪','name': 'Грузия'}, {'emoji': '🇧🇬','name': 'Болгария'}, {'emoji': '🇷🇴','name': 'Румыния'}, {'emoji': '🇹🇷','name': 'Турция'}, {'emoji': '🇮🇹','name': 'Италия'}, {'emoji': '🇸🇰','name': 'Словакия'}, {'emoji': '🇸🇦','name': 'Саудовская аравия'} ] # Supported types of jobs works = [ "IT", "Завод", "Финансы, банкинг", "Строительство", "Добыча ископаемых", "Обслуживание", "Медицина", "Работа с персоналом", "Преподавание" ] # Age of people ages = [ "от 0 до 18", "от 18 до 25", "от 26 до 35", "от 36 до 50", "от 50 до смерти", ] sexes = [ "Мужчина", "Женщина" ] cars = [ "Есть машина", "Нет машины" ] menu = [ "Начать опрос", "Показать статистику", "Настроить статистику" ] format_settings_menu = [ "в абсолютных числах", "в процентах", ] division_type = format_settings_menu[0]

    formaters.py

    def in_percent(value: int, amount: int, suffix: str = "", prefix: str = "\t") -> str: return prefix + str(round(value * 100 / amount)) + "%" + suffix def in_absolute(value: int, amount: int, suffix: str = "", prefix: str = "\t") -> str: return prefix + str(value) + suffix

    placers.py

    from config import ages, countries, sexes, works, cars def place_age(template: str, users: list, formater: callable) -> str: for age in ages: age_num = 0 for user in users: if age in user['age']: age_num += 1 pos_to_insert = template.find("\n", template.find(age)) template = template[:pos_to_insert] + formater(age_num, len(users)) + template[pos_to_insert:] return template def place_country(template: str, users: list, formater: callable) -> str: for country in countries: country_num = 0 for user in users: if country["name"] in user['country']: country_num += 1 if country_num > 0: pos_to_insert = template.find('\n', template.find("Страна")) template = template[:pos_to_insert] + formater(country_num, len(users), prefix="\n\t• " + country["emoji"] + " " + country["name"] + "\t") + template[pos_to_insert:] return template def place_sex(template: str, users: list, formater: callable) -> str: for sex in sexes: sex_num = 0 for user in users: if sex in user['sex']: sex_num += 1 pos_to_insert = template.find('\n', template.find(sex[:5])) template = template[:pos_to_insert] + formater(sex_num, len(users)) + template[pos_to_insert:] return template def place_work(template: str, users: list, formater: callable) -> str: for work in works: profession_num = 0 for user in users: if work in user['work']: profession_num += 1 pos_to_insert = template.find('\n', template.find(work)) template = template[: pos_to_insert] + formater(profession_num, len(users)) + template[pos_to_insert:] return template def place_car(template: str, users: list, formater: callable) -> str: for car in cars: has_car_num = 0 for user in users: if car in user['car']: has_car_num += 1 pos_to_insert = template.find('\n', template.find(car)) template = template[: pos_to_insert] + formater(has_car_num, len(users)) + template[pos_to_insert:] return template

    utils.py

    import json async def update_user(user_id: str, key: str, value) -> None: with open(f"user_{user_id}.json", "r", encoding="utf-8") as file: user_values = json.load(file) user_values[key] = value with open(f"user_{user_id}.json", "w", encoding="utf-8") as file: json.dump(user_values, file) async def is_user_completed(user_id: str) -> bool: with open(f"user_{user_id}.json", "r", encoding="utf-8") as file: user_values = json.load(file) user_values["is_complete"] = True for key in user_values: if user_values[key] is None: user_values["is_complete"] = False return False return True

    result_template.md

    *Возраст*, divisiontype: • от 0 до 18: • от 18 до 25: • от 26 до 35: • от 36 до 50: • от 50 до смерти: *Страна*, divisiontype: *Пол*, divisiontype: • \(♂\)Мужчины • \(♀\)Женщины *Профессия*, divisiontype: • IT • Завод • Финансы, банкинг • Строительство • Добыча ископаемых • Обслуживание • Медицина • Работа с персоналом • Преподавание *Есть ли машина*, divisiontype: • Есть машина • Нет машины

    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.

    Do not forget to share, like and leave a comment :)

    Comments

    (1)

    captcha
    Send
    LOADING ...
    Часы
    July 25, 2025, 11:20 p.m.
    Человек
    TOBI
    hi, could you make a telegram quiz bot that lets you upload a file (json) from which it extracts the data to create the quiz
    All replies (1)
    Reply
    LOADING ...

    Other

    Similar articles


    How to deploy(host) telegram bot on vps

    Clock
    19.01.2024
    /
    Clock
    05.10.2025
    An eye
    5270
    Hearts
    1
    Connected dots
    0
    Connected dots
    0
    Connected dots
    0
    It is a guide for deploying a telegram bot on VPS. This bot will be written in Python/aiogram. Also you will know how to install, run, and update it. As …

    Types and features of Telegram bots

    Clock
    22.12.2024
    /
    Clock
    02.10.2025
    An eye
    4108
    Hearts
    0
    Connected dots
    0
    Connected dots
    0
    Connected dots
    0
    Here you will learn the classification of telegram bots and the capabilities they have

    How to turn on the telegram bot’s inline mode, and how to build one

    Clock
    10.01.2025
    /
    Clock
    02.10.2025
    An eye
    2660
    Hearts
    0
    Connected dots
    0
    Connected dots
    0
    Connected dots
    0
    How to implement an inline telegram bot or activate inline mode for an already existing Telegram bot and how to use it. The entire example code is available, and the …

    Used termins


    Related questions