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.
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.
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.
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.