1
0
mirror of https://github.com/eternnoir/pyTelegramBotAPI.git synced 2023-08-10 21:12:57 +03:00

Merge pull request #1336 from coder2020official/master

File support for states
This commit is contained in:
Badiboy 2021-10-14 17:23:43 +03:00 committed by GitHub
commit b979c2fa1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 154 additions and 15 deletions

View File

@ -61,4 +61,8 @@ def age_incorrect(message):
bot.add_custom_filter(custom_filters.StateFilter(bot)) bot.add_custom_filter(custom_filters.StateFilter(bot))
bot.add_custom_filter(custom_filters.IsDigitFilter()) bot.add_custom_filter(custom_filters.IsDigitFilter())
# set saving states into file.
bot.enable_saving_states() # you can delete this if you do not need to save states
bot.infinity_polling(skip_pending=True) bot.infinity_polling(skip_pending=True)

View File

@ -27,7 +27,7 @@ logger.addHandler(console_output_handler)
logger.setLevel(logging.ERROR) logger.setLevel(logging.ERROR)
from telebot import apihelper, util, types from telebot import apihelper, util, types
from telebot.handler_backends import MemoryHandlerBackend, FileHandlerBackend, State from telebot.handler_backends import MemoryHandlerBackend, FileHandlerBackend, StateMemory, StateFile
REPLY_MARKUP_TYPES = Union[ REPLY_MARKUP_TYPES = Union[
@ -188,7 +188,8 @@ class TeleBot:
self.custom_filters = {} self.custom_filters = {}
self.state_handlers = [] self.state_handlers = []
self.current_states = State() self.current_states = StateMemory()
if apihelper.ENABLE_MIDDLEWARE: if apihelper.ENABLE_MIDDLEWARE:
self.typed_middleware_handlers = { self.typed_middleware_handlers = {
@ -237,6 +238,17 @@ class TeleBot:
""" """
self.next_step_backend = FileHandlerBackend(self.next_step_backend.handlers, filename, delay) self.next_step_backend = FileHandlerBackend(self.next_step_backend.handlers, filename, delay)
def enable_saving_states(self, filename="./.state-save/states.pkl"):
"""
Enable saving states (by default saving disabled)
:param filename: Filename of saving file
"""
self.current_states = StateFile(filename=filename)
self.current_states._create_dir()
def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"): def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"):
""" """
Enable saving reply handlers (by default saving disable) Enable saving reply handlers (by default saving disable)
@ -345,7 +357,7 @@ class TeleBot:
""" """
return apihelper.delete_webhook(self.token, drop_pending_updates, timeout) return apihelper.delete_webhook(self.token, drop_pending_updates, timeout)
def get_webhook_info(self, timeout=None): def get_webhook_info(self, timeout: Optional[int]=None):
""" """
Use this method to get current webhook status. Requires no parameters. Use this method to get current webhook status. Requires no parameters.
If the bot is using getUpdates, will return an object with the url field empty. If the bot is using getUpdates, will return an object with the url field empty.
@ -2369,7 +2381,7 @@ class TeleBot:
chat_id = message.chat.id chat_id = message.chat.id
self.register_next_step_handler_by_chat_id(chat_id, callback, *args, **kwargs) self.register_next_step_handler_by_chat_id(chat_id, callback, *args, **kwargs)
def set_state(self, chat_id, state): def set_state(self, chat_id: int, state: Union[int, str]):
""" """
Sets a new state of a user. Sets a new state of a user.
:param chat_id: :param chat_id:
@ -2377,7 +2389,7 @@ class TeleBot:
""" """
self.current_states.add_state(chat_id, state) self.current_states.add_state(chat_id, state)
def delete_state(self, chat_id): def delete_state(self, chat_id: int):
""" """
Delete the current state of a user. Delete the current state of a user.
:param chat_id: :param chat_id:
@ -2385,10 +2397,10 @@ class TeleBot:
""" """
self.current_states.delete_state(chat_id) self.current_states.delete_state(chat_id)
def retrieve_data(self, chat_id): def retrieve_data(self, chat_id: int):
return self.current_states.retrieve_data(chat_id) return self.current_states.retrieve_data(chat_id)
def get_state(self, chat_id): def get_state(self, chat_id: int):
""" """
Get current state of a user. Get current state of a user.
:param chat_id: :param chat_id:
@ -2396,7 +2408,7 @@ class TeleBot:
""" """
return self.current_states.current_state(chat_id) return self.current_states.current_state(chat_id)
def add_data(self, chat_id, **kwargs): def add_data(self, chat_id: int, **kwargs):
""" """
Add data to states. Add data to states.
:param chat_id: :param chat_id:

View File

@ -158,9 +158,9 @@ class StateFilter(AdvancedCustomFilter):
key = 'state' key = 'state'
def check(self, message, text): def check(self, message, text):
if self.bot.current_states.current_state(message.from_user.id) is False:return False if self.bot.current_states.current_state(message.from_user.id) is False: return False
elif text == '*':return True elif text == '*': return True
elif type(text) is list:return self.bot.current_states.current_state(message.from_user.id) in text elif type(text) is list: return self.bot.current_states.current_state(message.from_user.id) in text
return self.bot.current_states.current_state(message.from_user.id) == text return self.bot.current_states.current_state(message.from_user.id) == text
class IsDigitFilter(SimpleCustomFilter): class IsDigitFilter(SimpleCustomFilter):

View File

@ -143,7 +143,7 @@ class RedisHandlerBackend(HandlerBackend):
return handlers return handlers
class State: class StateMemory:
def __init__(self): def __init__(self):
self._states = {} self._states = {}
@ -166,7 +166,7 @@ class State:
def delete_state(self, chat_id): def delete_state(self, chat_id):
"""Delete a state""" """Delete a state"""
return self._states.pop(chat_id) self._states.pop(chat_id)
def _get_data(self, chat_id): def _get_data(self, chat_id):
return self._states[chat_id]['data'] return self._states[chat_id]['data']
@ -195,7 +195,7 @@ class State:
Save input text. Save input text.
Usage: Usage:
with state.retrieve_data(message.chat.id) as data: with bot.retrieve_data(message.chat.id) as data:
data['name'] = message.text data['name'] = message.text
Also, at the end of your 'Form' you can get the name: Also, at the end of your 'Form' you can get the name:
@ -203,11 +203,114 @@ class State:
""" """
return StateContext(self, chat_id) return StateContext(self, chat_id)
class StateFile:
"""
Class to save states in a file.
"""
def __init__(self, filename):
self.file_path = filename
def add_state(self, chat_id, state):
"""
Add a state.
:param chat_id:
:param state: new state
"""
states_data = self._read_data()
if chat_id in states_data:
states_data[chat_id]['state'] = state
return self._save_data(states_data)
else:
new_data = states_data[chat_id] = {'state': state,'data': {}}
return self._save_data(states_data)
def current_state(self, chat_id):
"""Current state."""
states_data = self._read_data()
if chat_id in states_data: return states_data[chat_id]['state']
else: return False
def delete_state(self, chat_id):
"""Delete a state"""
states_data = self._read_data()
states_data.pop(chat_id)
self._save_data(states_data)
def _read_data(self):
"""
Read the data from file.
"""
file = open(self.file_path, 'rb')
states_data = pickle.load(file)
file.close()
return states_data
def _create_dir(self):
"""
Create directory .save-handlers.
"""
dirs = self.file_path.rsplit('/', maxsplit=1)[0]
os.makedirs(dirs, exist_ok=True)
if not os.path.isfile(self.file_path):
with open(self.file_path,'wb') as file:
pickle.dump({}, file)
def _save_data(self, new_data):
"""
Save data after editing.
:param new_data:
"""
with open(self.file_path, 'wb+') as state_file:
pickle.dump(new_data, state_file, protocol=pickle.HIGHEST_PROTOCOL)
return True
def _get_data(self, chat_id):
return self._read_data()[chat_id]['data']
def set(self, chat_id, new_state):
"""
Set a new state for a user.
:param chat_id:
:param new_state: new_state of a user
"""
self.add_state(chat_id,new_state)
def _add_data(self, chat_id, key, value):
states_data = self._read_data()
result = states_data[chat_id]['data'][key] = value
self._save_data(result)
return result
def finish(self, chat_id):
"""
Finish(delete) state of a user.
:param chat_id:
"""
self.delete_state(chat_id)
def retrieve_data(self, chat_id):
"""
Save input text.
Usage:
with bot.retrieve_data(message.chat.id) as data:
data['name'] = message.text
Also, at the end of your 'Form' you can get the name:
data['name']
"""
return StateFileContext(self, chat_id)
class StateContext: class StateContext:
""" """
Class for data. Class for data.
""" """
def __init__(self , obj: State, chat_id) -> None: def __init__(self , obj: StateMemory, chat_id) -> None:
self.obj = obj self.obj = obj
self.chat_id = chat_id self.chat_id = chat_id
self.data = obj._get_data(chat_id) self.data = obj._get_data(chat_id)
@ -217,3 +320,23 @@ class StateContext:
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
return return
class StateFileContext:
"""
Class for data.
"""
def __init__(self , obj: StateFile, chat_id) -> None:
self.obj = obj
self.chat_id = chat_id
self.data = self.obj._get_data(self.chat_id)
def __enter__(self):
return self.data
def __exit__(self, exc_type, exc_val, exc_tb):
old_data = self.obj._read_data()
for i in self.data:
old_data[self.chat_id]['data'][i] = self.data.get(i)
self.obj._save_data(old_data)
return