Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StatesV2: Support for topics, multibots, and business messages #2321

Merged
merged 26 commits into from
Aug 11, 2024
Merged
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fce1c3d
Added statesv2
coder2020official Jun 23, 2024
7e5a044
Sync states v2 early version
coder2020official Jul 8, 2024
4a7bc5d
all update types are supported for states(theoretically)
coder2020official Jul 8, 2024
90a8f32
Merge branch 'master' into statesv2
coder2020official Jul 8, 2024
5e4598d
Merge branch 'eternnoir:master' into statesv2
coder2020official Jul 12, 2024
676597c
added redis support(not fully tested)
coder2020official Jul 12, 2024
5bd4271
Added redis dependency check
coder2020official Jul 12, 2024
a79fd77
fix test
coder2020official Jul 12, 2024
c29bf0e
Added pickle and renamed method
coder2020official Jul 12, 2024
15bced9
Fixed bugs, renamed StateContext for retrieve_data to StateDataContex…
coder2020official Jul 17, 2024
ff6485d
Async version fully supported, partially tested.
coder2020official Jul 19, 2024
af79db6
rewrote pickle sync with lock decorators, few changes to storages to …
coder2020official Jul 21, 2024
1adca13
Improved docstrings, fixed bugs, allow accessing statecontext via sta…
coder2020official Jul 21, 2024
adae7a9
Removed aioredis from optional dependencies, breaking change made for…
coder2020official Jul 21, 2024
d33db85
Updated examples
coder2020official Jul 21, 2024
536ffa2
Added migrate_format to migrate from old format of states to new. Onl…
coder2020official Jul 25, 2024
d2485bf
Another approach to bot id
coder2020official Jul 26, 2024
dd0dfa9
fix tests
coder2020official Jul 26, 2024
7f99176
fixed redis data bug on set_state
coder2020official Jul 26, 2024
3fec98c
Code cleanup
coder2020official Jul 27, 2024
dbfa514
code cleanuop & renamed aio -> asyncio
coder2020official Jul 27, 2024
2dbf190
Remove apihelper following the validate_token not using getMe
coder2020official Jul 28, 2024
b10e8d7
Reverted changes regarding self._user, fixed validate_token=False cau…
coder2020official Jul 28, 2024
6108e35
fix docstring
coder2020official Jul 28, 2024
30ebe75
make extract_bot_id return None in case validation fails
coder2020official Jul 30, 2024
d44ebce
Fix versions
coder2020official Aug 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 132 additions & 69 deletions examples/asynchronous_telebot/custom_states.py
Original file line number Diff line number Diff line change
@@ -1,91 +1,154 @@
from telebot import asyncio_filters
from telebot.async_telebot import AsyncTeleBot

# list of storages, you can use any storage
from telebot import async_telebot, asyncio_filters, types
from telebot.asyncio_storage import StateMemoryStorage
from telebot.states import State, StatesGroup
from telebot.states.asyncio.context import StateContext

# new feature for states.
from telebot.asyncio_handler_backends import State, StatesGroup

# default state storage is statememorystorage
bot = AsyncTeleBot('TOKEN', state_storage=StateMemoryStorage())
# Initialize the bot
state_storage = StateMemoryStorage() # don't use this in production; switch to redis
bot = async_telebot.AsyncTeleBot("TOKEN", state_storage=state_storage)


# Just create different statesgroup
# Define states
class MyStates(StatesGroup):
name = State() # statesgroup should contain states
surname = State()
name = State()
age = State()
color = State()
hobby = State()


# Start command handler
@bot.message_handler(commands=["start"])
async def start_ex(message: types.Message, state: StateContext):
await state.set(MyStates.name)
await bot.send_message(
message.chat.id,
"Hello! What is your first name?",
reply_to_message_id=message.message_id,
)

# set_state -> sets a new state
# delete_state -> delets state if exists
# get_state -> returns state if exists

# Cancel command handler
@bot.message_handler(state="*", commands=["cancel"])
async def any_state(message: types.Message, state: StateContext):
await state.delete()
await bot.send_message(
message.chat.id,
"Your information has been cleared. Type /start to begin again.",
reply_to_message_id=message.message_id,
)

@bot.message_handler(commands=['start'])
async def start_ex(message):
"""
Start command. Here we are starting state
"""
await bot.set_state(message.from_user.id, MyStates.name, message.chat.id)
await bot.send_message(message.chat.id, 'Hi, write me a name')



@bot.message_handler(state="*", commands='cancel')
async def any_state(message):
"""
Cancel state
"""
await bot.send_message(message.chat.id, "Your state was cancelled.")
await bot.delete_state(message.from_user.id, message.chat.id)

# Handler for name input
@bot.message_handler(state=MyStates.name)
async def name_get(message):
"""
State 1. Will process when user's state is MyStates.name.
"""
await bot.send_message(message.chat.id, f'Now write me a surname')
await bot.set_state(message.from_user.id, MyStates.surname, message.chat.id)
async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
data['name'] = message.text


@bot.message_handler(state=MyStates.surname)
async def ask_age(message):
"""
State 2. Will process when user's state is MyStates.surname.
"""
await bot.send_message(message.chat.id, "What is your age?")
await bot.set_state(message.from_user.id, MyStates.age, message.chat.id)
async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
data['surname'] = message.text

# result
async def name_get(message: types.Message, state: StateContext):
await state.set(MyStates.age)
await bot.send_message(
message.chat.id, "How old are you?", reply_to_message_id=message.message_id
)
await state.add_data(name=message.text)


# Handler for age input
@bot.message_handler(state=MyStates.age, is_digit=True)
async def ready_for_answer(message):
"""
State 3. Will process when user's state is MyStates.age.
"""
async with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
await bot.send_message(message.chat.id, "Ready, take a look:\n<b>Name: {name}\nSurname: {surname}\nAge: {age}</b>".format(name=data['name'], surname=data['surname'], age=message.text), parse_mode="html")
await bot.delete_state(message.from_user.id, message.chat.id)

#incorrect number
async def ask_color(message: types.Message, state: StateContext):
await state.set(MyStates.color)
await state.add_data(age=message.text)

# Define reply keyboard for color selection
keyboard = types.ReplyKeyboardMarkup(row_width=2)
colors = ["Red", "Green", "Blue", "Yellow", "Purple", "Orange", "Other"]
buttons = [types.KeyboardButton(color) for color in colors]
keyboard.add(*buttons)

await bot.send_message(
message.chat.id,
"What is your favorite color? Choose from the options below.",
reply_markup=keyboard,
reply_to_message_id=message.message_id,
)


# Handler for color input
@bot.message_handler(state=MyStates.color)
async def ask_hobby(message: types.Message, state: StateContext):
await state.set(MyStates.hobby)
await state.add_data(color=message.text)

# Define reply keyboard for hobby selection
keyboard = types.ReplyKeyboardMarkup(row_width=2)
hobbies = ["Reading", "Traveling", "Gaming", "Cooking"]
buttons = [types.KeyboardButton(hobby) for hobby in hobbies]
keyboard.add(*buttons)

await bot.send_message(
message.chat.id,
"What is one of your hobbies? Choose from the options below.",
reply_markup=keyboard,
reply_to_message_id=message.message_id,
)


# Handler for hobby input; use filters to ease validation
@bot.message_handler(
state=MyStates.hobby, text=["Reading", "Traveling", "Gaming", "Cooking"]
)
async def finish(message: types.Message, state: StateContext):
async with state.data() as data:
name = data.get("name")
age = data.get("age")
color = data.get("color")
hobby = message.text # Get the hobby from the message text

# Provide a fun fact based on color
color_facts = {
"Red": "Red is often associated with excitement and passion.",
"Green": "Green is the color of nature and tranquility.",
"Blue": "Blue is known for its calming and serene effects.",
"Yellow": "Yellow is a cheerful color often associated with happiness.",
"Purple": "Purple signifies royalty and luxury.",
"Orange": "Orange is a vibrant color that stimulates enthusiasm.",
"Other": "Colors have various meanings depending on context.",
}
color_fact = color_facts.get(
color, "Colors have diverse meanings, and yours is unique!"
)

msg = (
f"Thank you for sharing! Here is a summary of your information:\n"
f"First Name: {name}\n"
f"Age: {age}\n"
f"Favorite Color: {color}\n"
f"Fun Fact about your color: {color_fact}\n"
f"Favorite Hobby: {hobby}"
)

await bot.send_message(
message.chat.id, msg, parse_mode="html", reply_to_message_id=message.message_id
)
await state.delete()


# Handler for incorrect age input
@bot.message_handler(state=MyStates.age, is_digit=False)
async def age_incorrect(message):
"""
Will process for wrong input when state is MyState.age
"""
await bot.send_message(message.chat.id, 'Looks like you are submitting a string in the field age. Please enter a number')
async def age_incorrect(message: types.Message):
await bot.send_message(
message.chat.id,
"Please enter a valid number for age.",
reply_to_message_id=message.message_id,
)

# register filters

# Add custom filters
bot.add_custom_filter(asyncio_filters.StateFilter(bot))
bot.add_custom_filter(asyncio_filters.IsDigitFilter())
bot.add_custom_filter(asyncio_filters.TextMatchFilter())

# necessary for state parameter in handlers.
from telebot.states.asyncio.middleware import StateMiddleware

bot.setup_middleware(StateMiddleware(bot))

# Start polling
import asyncio
asyncio.run(bot.polling())

asyncio.run(bot.polling())
Loading