Бот анонимных вопросов на телеграм, или квиз бот.
Вступление
Привет в этом кейсе я продемонстрирую тебе, как я сделал своего бота опросника.
Приведу исходный код python. Объясню структуру, и предназначение каждого файла
и обработчика.Ну и конечно сможешь по взаимодействовать с моим, уже готовым ботом.
Ты сможешь найти его через:
- Ссылку https://t.me/TimQuizzerBot
- Или вбив название бота, TimQuizzer в телеграме
Увы, когда я его делал, я незадумывал его мультиязычным. А когда спохватился, всё переделывать не решился. Да и
суть данного бота не в мультиязычности. Но к следующему разу, я точно сделаю мультиязычного бота. Так что всем не русскоговорящим будет легче.
Пример создания телеграм бота-опроса.
Структура бота-опроса
Мой бот имеет линейную/последовательную структуру. С некоторыми 'плавающими' обработчиками.
Что я под этим подразумеваю ?
Так как у меня бот-опросник. И он должен задавать вопросы последовательно, один за другим.
То и обработчики, которые обрабатывают ответы пользователя, должны вызываться последовательно, друг за другом.
Под 'плавающими' обработчиками, я имел в виду, те функции, которые могут быть вызваны в любой момент общения с ботом.
Их 5:
- /start ➩ Стартует бота. Приветственное сообщение.
- /help ➩ Показывает доступные команды
- /menu ➩ Показывает доступные команды
- /settings ➩ Настройка вывода результата
- /result ➩ Выводит результат опросов людей
Структура проекта бота-опросника.
- .env ➩ Файл содержащий токен бота. При скачивании с репозитория, его не будет. Нужно сделать его самостоятельно.
- data.json ➩ Хранит данные обо всех пользователя. База данных. Да, пока без MySQL и прочих SQL. Создаётся автоматически.
- result_template.md ➩ Шаблон, который используется для вывода информации.
- requirements.txt ➩ Необходимые пакеты, для работы в виртуальном окружении
- main.py ➩ Точка входа. Содержит все обработчики запросов
- config.py ➩ Конфигурационный файл. Содержит глобальные переменные.
- formaters.py ➩ Функции, суть которых в том, чтобы была возможность изменять вывод результатов в сообщении.
- placers.py ➩ Функции взаимодействуюшие с шаблоном. Вставляют данные в него.
- utils.py ➩ Различные утилиты.
Команда /start
Приветствует новоприбывших. Создаёт кнопки в сообщении, где предлагает пройти опрос.
@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))
Страна опрашиваемого
Создаёт словарь user_values, и сразу же присваивает ей возраст и id пользователя.
Создаёт кнопки на клавиатуре, с эмоджи стран на них. И само-сабой спрашивает страну опрашиваемого.
@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))
Пол опрашиваемого
Ловит текст выбранной страны и добавляет в user_values.
Спрашивает пол, и создаёт кнопки на клавиатуре со значками ♂ и ♀
@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))
Работа опрашиваемого
Обрабатывает текст от предыдущего обработчика, your_sex_handler, добавляет пол пользователю.
Спрашивает про работу, создаёт кнопки в сообщении.
@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)
)
Машина опрашиваемого
Сохраняет значение работы пользователя в user_values
Спрашивает, есть ли машина ?
Создаёт кнопки на клавиатуре
@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))
Конец опроса
Добавляет информацию о машине, пользователю.
После чего, сохраняет пользователя в базу данных data.json. Если конечно, пользователь
полностью прошёл опрос, а не случайно вызвал обработчик в середине.
Возможно, стоило бы добавить проверку на повторяющихся пользователей. Но я этого делать не стал,
ибо хотел дать пользователям возможность поучаствовать несколько раз.
Выводит стандартное сообщение и кнопку в сообщении с возможностью показать результат.
@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())
Главное меню
Данный обработчик можно вызвать через команды */help* или */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())
Настройки
Данный обработчик разбит на 4 функции. Одна, *setting_update_format_numbers*,
реализует непосредственно функционал изменения формата отображаемой информации.
@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())
Другая, *setting*, показывает возможные настройки, создаёт соответствующие
кнопки.
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())
И две другие реализованы через *callback_query* и *message*. И обе они вызывают
*setting*. Сделано это было для того, чтобы была возможность работать с этой функцией,
и как с командой и как кнопкой.
@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", другой для вызова
через кнопки. Обе, они имеют один и тот же функционал *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)
Сам же обработчик *result*, занимается тем что форматирует result_template.md.
Вставляет данные при помощи функций из *placers.py*. А формат меняет при помощи *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)
Скачать готового телеграм бота, или скопировать код
Ты можешь скачать бота с моего репозитория.
TimQuizzer-bot
Или ты можешь просмотреть исходный код, ниже. Скопировав интересующие тебя части
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:
• Есть машина
• Нет машины
Заключение
Вот так вот я написал своего квиз бота. Возможно вам покажется он простеньким, и вы вцелом будете правы.
Но нужно же публиковать статьи, верно? В любом случае, надеюсь данный кейс сможет помочь тебе в написании
собственного бота опросника.
5
1