How to create telegram bot with Aiogram and Finite State Machine

I decided to understand Finite State Machine – because my bots had one function up to this point. These were simple bots for one task – to process a request and generate a response.

For more complex systems, it is necessary to determine the current states of the bot – in which mode bot works.

For an educational mini project, I decided to write a bot that determines the compatibility between zodiac signs. This, of course, is not at all serious and the bot should be understood as a joke, but according to reviews, some people was upset/delighted with the results, which indicates that there are people who believe in the horoscope. LOL.

There is a source code with comments: Github link

from aiogram import Bot, types
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
from aiogram.types import ContentType
from aiogram.dispatcher import Dispatcher, FSMContext
from aiogram.dispatcher.filters.state import State, StatesGroup
from aiogram.utils import executor
from aiogram.contrib.fsm_storage.memory import MemoryStorage
import os
from datetime import datetime
import random


from time import sleep #import sleep for timeout set
logfilename = 'log.csv' #difining logfile
storage=MemoryStorage() #defining storage in memory
bot = Bot(token=os.getenv('TOKEN'))
dp = Dispatcher(bot, storage=storage)
button1 = KeyboardButton('Овен')
button2 = KeyboardButton('Телец')
button3 = KeyboardButton('Близнецы')
button4 = KeyboardButton('Рак')
button5 = KeyboardButton('Лев')
button6 = KeyboardButton('Дева')
button7 = KeyboardButton('Весы')
button8 = KeyboardButton('Скорпион')
button9 = KeyboardButton('Стрелец')
button10 = KeyboardButton('Козерог')
button11 = KeyboardButton('Водолей')
button12 = KeyboardButton('Рыбы')
#keyboard buttons for every zodiac sign

keyboard1 = ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True).add(button1).add(button2).add(button3).add(button4).add(button5).add(button6).add(button7).add(button8).add(button9).add(button10).add(button11).add(button12)
#whole keyboard


class FSMSay(StatesGroup):
    first = State()
    second = State()
#defining bot states

@dp.message_handler()
async def command_start(message : types.Message):
    await bot.send_message(message.from_user.id, 'Привет! Узнай вашу совместимость😀 Данные берутся со звезд онлайн, зависят от момента времени, разработчик ответственности не несет! Выбери свой знак:', reply_markup=keyboard1)
    await FSMSay.first.set() #setting first state
    userid = message.chat.id
    user_first_name = str(message.chat.first_name)
    user_last_name = str(message.chat.last_name)
    user_username = str(message.chat.username)
    timenow = datetime.now()
    with open(logfilename, 'a', encoding="utf-8") as file_object:
        file_object.write(f'"{timenow}","{userid}","{user_username}","{user_first_name} {user_last_name}","request","{message.text}"\n')
#logging


@dp.message_handler(state=FSMSay.first)
async def command_start(message : types.Message, state: FSMContext):
    await FSMSay.second.set() #setting second state
    async with state.proxy() as data:
        data['znak1'] = message.text #saving first sign in state proxy storage
    await bot.send_message(message.from_user.id, 'Выбери его/ее знаек:', reply_markup=keyboard1)
    userid = message.chat.id
    user_first_name = str(message.chat.first_name)
    user_last_name = str(message.chat.last_name)
    user_username = str(message.chat.username)
    timenow = datetime.now()
    with open(logfilename, 'a', encoding="utf-8") as file_object:
        file_object.write(f'"{timenow}","{userid}","{user_username}","{user_first_name} {user_last_name}","request","{message.text}"\n')
#logging

@dp.message_handler(state=FSMSay.second)
async def command_start(message : types.Message, state: FSMContext):
    async with state.proxy() as data:
        data['znak2'] = message.text
    if data['znak1'] == 'Рак' or data['znak2'] == 'Рак':
        result = 'Совместимость ' + data['znak1'] + ' и ' + data['znak2']+ ' = ' + random.choice(['🔥','❤','🤰'])
    else:
        result = 'Совместимость ' + data['znak1'] + ' и ' + data['znak2']+ ' = ' + random.choice(['🔥','❤','💩','🤰','😔','💩'])
    #my space algorithm =D
    await bot.send_message(message.from_user.id, result)
    await state.finish() #closing second state
    sleep(2)
    await bot.send_message(message.from_user.id, 'Понравилось? Давай еще =)')
    sleep(1)
    await bot.send_message(message.from_user.id, 'Выбери свой знак:', reply_markup=keyboard1)
    await FSMSay.first.set()
    userid = message.chat.id
    user_first_name = str(message.chat.first_name)
    user_last_name = str(message.chat.last_name)
    user_username = str(message.chat.username)
    timenow = datetime.now()
    with open(logfilename, 'a', encoding="utf-8") as file_object:
        file_object.write(f'"{timenow}","{userid}","{user_username}","{user_first_name} {user_last_name}","request","{message.text}","{result}"\n')
    



executor.start_polling(dp, skip_updates=True)

This is a Python script that uses the aiogram library to build a Telegram bot that tells the compatibility between two zodiac signs. The script defines a set of message handlers to process incoming messages from users, and uses a finite-state machine (FSM) to keep track of the user’s progress through the conversation.

See also  Updated Django model days counting - month start error

The script starts by defining a set of keyboard buttons, which represent the twelve zodiac signs. It then defines a FSM with two states, “first” and “second”. The “first” state is set when the user first starts interacting with the bot and chooses their own zodiac sign. The “second” state is set when the user chooses the zodiac sign of the person they want to check compatibility with. The FSM allows the bot to keep track of the user’s progress and prompt them for the necessary information.

The script defines a message handler for the “/start” command, which sends a welcome message and sets the initial FSM state to “first”. It also writes a log of the user’s interaction to a CSV file.

See also  How to add Matplotlib to Django

The script defines two more message handlers, one for each of the two FSM states. These handlers prompt the user for the necessary information (either their own zodiac sign or the zodiac sign of the person they want to check compatibility with), store the information in the FSM state proxy storage, and update the FSM state to “second” (when the user is choosing the second zodiac sign) or finish the FSM state (when the user has chosen both zodiac signs).

Finally, the script defines a message handler to calculate and display the compatibility between the two zodiac signs, and prompts the user to choose their own zodiac sign again to start a new compatibility check. The script also writes a log of the user’s interaction, including the result of the compatibility check, to the CSV file.

See also  Automatic gracefully shutdown of virtual machines and ESXI hypervisor from telegram bot

The script uses a sleep function to add pauses between some of the bot’s messages to make the conversation feel more natural. The script also uses the Python random module to choose a random compatibility score emoji for display.

Author: admin

Leave a Reply

Your email address will not be published. Required fields are marked *