mirror of
https://github.com/ada-dmitry/kurator_bot.git
synced 2026-09-24 08:10:15 +00:00
Init
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
from telebot.storage.memory_storage import StateMemoryStorage
|
||||
from telebot.storage.redis_storage import StateRedisStorage
|
||||
from telebot.storage.pickle_storage import StatePickleStorage
|
||||
from telebot.storage.base_storage import StateDataContext, StateStorageBase
|
||||
|
||||
|
||||
__all__ = [
|
||||
"StateStorageBase",
|
||||
"StateDataContext",
|
||||
"StateMemoryStorage",
|
||||
"StateRedisStorage",
|
||||
"StatePickleStorage",
|
||||
]
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,118 @@
|
||||
import copy
|
||||
|
||||
|
||||
class StateStorageBase:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def set_data(self, chat_id, user_id, key, value):
|
||||
"""
|
||||
Set data for a user in a particular chat.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_data(self, chat_id, user_id):
|
||||
"""
|
||||
Get data for a user in a particular chat.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_state(self, chat_id, user_id, state):
|
||||
"""
|
||||
Set state for a particular user.
|
||||
|
||||
! Note that you should create a
|
||||
record if it does not exist, and
|
||||
if a record with state already exists,
|
||||
you need to update a record.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_state(self, chat_id, user_id):
|
||||
"""
|
||||
Delete state for a particular user.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def reset_data(self, chat_id, user_id):
|
||||
"""
|
||||
Reset data for a particular user in a chat.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_state(self, chat_id, user_id):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_interactive_data(self, chat_id, user_id):
|
||||
raise NotImplementedError
|
||||
|
||||
def save(self, chat_id, user_id, data):
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_key(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
prefix: str,
|
||||
separator: str,
|
||||
business_connection_id: str = None,
|
||||
message_thread_id: int = None,
|
||||
bot_id: int = None,
|
||||
) -> str:
|
||||
"""
|
||||
Convert parameters to a key.
|
||||
"""
|
||||
params = [prefix]
|
||||
if bot_id:
|
||||
params.append(str(bot_id))
|
||||
if business_connection_id:
|
||||
params.append(business_connection_id)
|
||||
if message_thread_id:
|
||||
params.append(str(message_thread_id))
|
||||
params.append(str(chat_id))
|
||||
params.append(str(user_id))
|
||||
|
||||
return separator.join(params)
|
||||
|
||||
|
||||
class StateDataContext:
|
||||
"""
|
||||
Class for data.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
obj,
|
||||
chat_id,
|
||||
user_id,
|
||||
business_connection_id=None,
|
||||
message_thread_id=None,
|
||||
bot_id=None,
|
||||
):
|
||||
self.obj = obj
|
||||
res = obj.get_data(
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
business_connection_id=business_connection_id,
|
||||
message_thread_id=message_thread_id,
|
||||
bot_id=bot_id,
|
||||
)
|
||||
self.data = copy.deepcopy(res)
|
||||
self.chat_id = chat_id
|
||||
self.user_id = user_id
|
||||
self.bot_id = bot_id
|
||||
self.business_connection_id = business_connection_id
|
||||
self.message_thread_id = message_thread_id
|
||||
|
||||
def __enter__(self):
|
||||
return self.data
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
return self.obj.save(
|
||||
self.chat_id,
|
||||
self.user_id,
|
||||
self.data,
|
||||
self.business_connection_id,
|
||||
self.message_thread_id,
|
||||
self.bot_id,
|
||||
)
|
||||
@@ -0,0 +1,225 @@
|
||||
from telebot.storage.base_storage import StateStorageBase, StateDataContext
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
class StateMemoryStorage(StateStorageBase):
|
||||
"""
|
||||
Memory storage for states.
|
||||
|
||||
Stores states in memory as a dictionary.
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
storage = StateMemoryStorage()
|
||||
bot = TeleBot(token, storage=storage)
|
||||
|
||||
:param separator: Separator for keys, default is ":".
|
||||
:type separator: Optional[str]
|
||||
|
||||
:param prefix: Prefix for keys, default is "telebot".
|
||||
:type prefix: Optional[str]
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, separator: Optional[str] = ":", prefix: Optional[str] = "telebot"
|
||||
) -> None:
|
||||
self.separator = separator
|
||||
self.prefix = prefix
|
||||
if not self.prefix:
|
||||
raise ValueError("Prefix cannot be empty")
|
||||
|
||||
self.data = (
|
||||
{}
|
||||
) # key: telebot:bot_id:business_connection_id:message_thread_id:chat_id:user_id
|
||||
|
||||
def set_state(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
state: str,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
if hasattr(state, "name"):
|
||||
state = state.name
|
||||
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
if self.data.get(_key) is None:
|
||||
self.data[_key] = {"state": state, "data": {}}
|
||||
else:
|
||||
self.data[_key]["state"] = state
|
||||
|
||||
return True
|
||||
|
||||
def get_state(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> Union[str, None]:
|
||||
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
if self.data.get(_key) is None:
|
||||
return None
|
||||
|
||||
return self.data[_key]["state"]
|
||||
|
||||
def delete_state(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
if self.data.get(_key) is None:
|
||||
return False
|
||||
|
||||
del self.data[_key]
|
||||
return True
|
||||
|
||||
def set_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
key: str,
|
||||
value: Union[str, int, float, dict],
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
if self.data.get(_key) is None:
|
||||
raise RuntimeError(f"StateMemoryStorage: key {_key} does not exist.")
|
||||
self.data[_key]["data"][key] = value
|
||||
return True
|
||||
|
||||
def get_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> dict:
|
||||
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
return self.data.get(_key, {}).get("data", {})
|
||||
|
||||
def reset_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
if self.data.get(_key) is None:
|
||||
return False
|
||||
self.data[_key]["data"] = {}
|
||||
return True
|
||||
|
||||
def get_interactive_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> Optional[dict]:
|
||||
return StateDataContext(
|
||||
self,
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
business_connection_id=business_connection_id,
|
||||
message_thread_id=message_thread_id,
|
||||
bot_id=bot_id,
|
||||
)
|
||||
|
||||
def save(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
data: dict,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
if self.data.get(_key) is None:
|
||||
return False
|
||||
self.data[_key]["data"] = data
|
||||
return True
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"<StateMemoryStorage: {self.data}>"
|
||||
@@ -0,0 +1,263 @@
|
||||
import os
|
||||
import pickle
|
||||
import threading
|
||||
from typing import Optional, Union, Callable
|
||||
from telebot.storage.base_storage import StateStorageBase, StateDataContext
|
||||
|
||||
|
||||
def with_lock(func: Callable) -> Callable:
|
||||
def wrapper(self, *args, **kwargs):
|
||||
with self.lock:
|
||||
return func(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class StatePickleStorage(StateStorageBase):
|
||||
"""
|
||||
State storage based on pickle file.
|
||||
|
||||
.. warning::
|
||||
|
||||
This storage is not recommended for production use.
|
||||
Data may be corrupted. If you face a case where states do not work as expected,
|
||||
try to use another storage.
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
storage = StatePickleStorage()
|
||||
bot = TeleBot(token, storage=storage)
|
||||
|
||||
:param file_path: Path to file where states will be stored.
|
||||
:type file_path: str
|
||||
|
||||
:param prefix: Prefix for keys, default is "telebot".
|
||||
:type prefix: Optional[str]
|
||||
|
||||
:param separator: Separator for keys, default is ":".
|
||||
:type separator: Optional[str]
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file_path: str = "./.state-save/states.pkl",
|
||||
prefix="telebot",
|
||||
separator: Optional[str] = ":",
|
||||
) -> None:
|
||||
self.file_path = file_path
|
||||
self.prefix = prefix
|
||||
self.separator = separator
|
||||
self.lock = threading.Lock()
|
||||
|
||||
self.create_dir()
|
||||
|
||||
def _read_from_file(self) -> dict:
|
||||
with open(self.file_path, "rb") as f:
|
||||
return pickle.load(f)
|
||||
|
||||
def _write_to_file(self, data: dict) -> None:
|
||||
with open(self.file_path, "wb") as f:
|
||||
pickle.dump(data, f)
|
||||
|
||||
def create_dir(self):
|
||||
"""
|
||||
Create directory .save-handlers.
|
||||
"""
|
||||
dirs, filename = os.path.split(self.file_path)
|
||||
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)
|
||||
|
||||
@with_lock
|
||||
def set_state(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
state: str,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
data = self._read_from_file()
|
||||
if _key not in data:
|
||||
data[_key] = {"state": state, "data": {}}
|
||||
else:
|
||||
data[_key]["state"] = state
|
||||
self._write_to_file(data)
|
||||
return True
|
||||
|
||||
@with_lock
|
||||
def get_state(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> Union[str, None]:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
data = self._read_from_file()
|
||||
return data.get(_key, {}).get("state")
|
||||
|
||||
@with_lock
|
||||
def delete_state(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
data = self._read_from_file()
|
||||
if _key in data:
|
||||
del data[_key]
|
||||
self._write_to_file(data)
|
||||
return True
|
||||
return False
|
||||
|
||||
@with_lock
|
||||
def set_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
key: str,
|
||||
value: Union[str, int, float, dict],
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
data = self._read_from_file()
|
||||
state_data = data.get(_key, {})
|
||||
state_data["data"][key] = value
|
||||
|
||||
if _key not in data:
|
||||
raise RuntimeError(f"PickleStorage: key {_key} does not exist.")
|
||||
|
||||
self._write_to_file(data)
|
||||
return True
|
||||
|
||||
@with_lock
|
||||
def get_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> dict:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
data = self._read_from_file()
|
||||
return data.get(_key, {}).get("data", {})
|
||||
|
||||
@with_lock
|
||||
def reset_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
data = self._read_from_file()
|
||||
if _key in data:
|
||||
data[_key]["data"] = {}
|
||||
self._write_to_file(data)
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_interactive_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> Optional[dict]:
|
||||
return StateDataContext(
|
||||
self,
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
business_connection_id=business_connection_id,
|
||||
message_thread_id=message_thread_id,
|
||||
bot_id=bot_id,
|
||||
)
|
||||
|
||||
@with_lock
|
||||
def save(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
data: dict,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
data = self._read_from_file()
|
||||
data[_key]["data"] = data
|
||||
self._write_to_file(data)
|
||||
return True
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"StatePickleStorage({self.file_path}, {self.prefix})"
|
||||
@@ -0,0 +1,324 @@
|
||||
import json
|
||||
from telebot.storage.base_storage import StateStorageBase, StateDataContext
|
||||
from typing import Optional, Union
|
||||
|
||||
redis_installed = True
|
||||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
redis_installed = False
|
||||
|
||||
|
||||
class StateRedisStorage(StateStorageBase):
|
||||
"""
|
||||
State storage based on Redis.
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
storage = StateRedisStorage(...)
|
||||
bot = TeleBot(token, storage=storage)
|
||||
|
||||
:param host: Redis host, default is "localhost".
|
||||
:type host: str
|
||||
|
||||
:param port: Redis port, default is 6379.
|
||||
:type port: int
|
||||
|
||||
:param db: Redis database, default is 0.
|
||||
:type db: int
|
||||
|
||||
:param password: Redis password, default is None.
|
||||
:type password: Optional[str]
|
||||
|
||||
:param prefix: Prefix for keys, default is "telebot".
|
||||
:type prefix: Optional[str]
|
||||
|
||||
:param redis_url: Redis URL, default is None.
|
||||
:type redis_url: Optional[str]
|
||||
|
||||
:param connection_pool: Redis connection pool, default is None.
|
||||
:type connection_pool: Optional[ConnectionPool]
|
||||
|
||||
:param separator: Separator for keys, default is ":".
|
||||
:type separator: Optional[str]
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host="localhost",
|
||||
port=6379,
|
||||
db=0,
|
||||
password=None,
|
||||
prefix="telebot",
|
||||
redis_url=None,
|
||||
connection_pool: "redis.ConnectionPool" = None,
|
||||
separator: Optional[str] = ":",
|
||||
) -> None:
|
||||
|
||||
if not redis_installed:
|
||||
raise ImportError(
|
||||
"Redis is not installed. Please install it via pip install redis"
|
||||
)
|
||||
|
||||
self.separator = separator
|
||||
self.prefix = prefix
|
||||
if not self.prefix:
|
||||
raise ValueError("Prefix cannot be empty")
|
||||
|
||||
if redis_url:
|
||||
self.redis = redis.Redis.from_url(redis_url)
|
||||
elif connection_pool:
|
||||
self.redis = redis.Redis(connection_pool=connection_pool)
|
||||
else:
|
||||
self.redis = redis.Redis(host=host, port=port, db=db, password=password)
|
||||
|
||||
def set_state(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
state: str,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
if hasattr(state, "name"):
|
||||
state = state.name
|
||||
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
def set_state_action(pipe):
|
||||
pipe.multi()
|
||||
|
||||
data = pipe.hget(_key, "data")
|
||||
result = pipe.execute()
|
||||
data = result[0]
|
||||
if data is None:
|
||||
# If data is None, set it to an empty dictionary
|
||||
data = {}
|
||||
pipe.hset(_key, "data", json.dumps(data))
|
||||
|
||||
pipe.hset(_key, "state", state)
|
||||
|
||||
self.redis.transaction(set_state_action, _key)
|
||||
return True
|
||||
|
||||
def get_state(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> Union[str, None]:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
state_bytes = self.redis.hget(_key, "state")
|
||||
return state_bytes.decode("utf-8") if state_bytes else None
|
||||
|
||||
def delete_state(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
return self.redis.delete(_key) > 0
|
||||
|
||||
def set_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
key: str,
|
||||
value: Union[str, int, float, dict],
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
def set_data_action(pipe):
|
||||
pipe.multi()
|
||||
data = pipe.hget(_key, "data")
|
||||
data = data.execute()[0]
|
||||
if data is None:
|
||||
raise RuntimeError(f"RedisStorage: key {_key} does not exist.")
|
||||
else:
|
||||
data = json.loads(data)
|
||||
data[key] = value
|
||||
pipe.hset(_key, "data", json.dumps(data))
|
||||
|
||||
self.redis.transaction(set_data_action, _key)
|
||||
return True
|
||||
|
||||
def get_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> dict:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
data = self.redis.hget(_key, "data")
|
||||
return json.loads(data) if data else {}
|
||||
|
||||
def reset_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
def reset_data_action(pipe):
|
||||
pipe.multi()
|
||||
if pipe.exists(_key):
|
||||
pipe.hset(_key, "data", "{}")
|
||||
else:
|
||||
return False
|
||||
|
||||
self.redis.transaction(reset_data_action, _key)
|
||||
return True
|
||||
|
||||
def get_interactive_data(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> Optional[dict]:
|
||||
return StateDataContext(
|
||||
self,
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
business_connection_id=business_connection_id,
|
||||
message_thread_id=message_thread_id,
|
||||
bot_id=bot_id,
|
||||
)
|
||||
|
||||
def save(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
data: dict,
|
||||
business_connection_id: Optional[str] = None,
|
||||
message_thread_id: Optional[int] = None,
|
||||
bot_id: Optional[int] = None,
|
||||
) -> bool:
|
||||
_key = self._get_key(
|
||||
chat_id,
|
||||
user_id,
|
||||
self.prefix,
|
||||
self.separator,
|
||||
business_connection_id,
|
||||
message_thread_id,
|
||||
bot_id,
|
||||
)
|
||||
|
||||
def save_action(pipe):
|
||||
pipe.multi()
|
||||
if pipe.exists(_key):
|
||||
pipe.hset(_key, "data", json.dumps(data))
|
||||
else:
|
||||
return False
|
||||
|
||||
self.redis.transaction(save_action, _key)
|
||||
return True
|
||||
|
||||
def migrate_format(self, bot_id: int, prefix: Optional[str] = "telebot_"):
|
||||
"""
|
||||
Migrate from old to new format of keys.
|
||||
Run this function once to migrate all redis existing keys to new format.
|
||||
|
||||
Starting from version 4.23.0, the format of keys has been changed:
|
||||
<key>:value
|
||||
- Old format: {prefix}chat_id: {user_id: {'state': None, 'data': {}}, ...}
|
||||
- New format:
|
||||
{prefix}{separator}{bot_id}{separator}{business_connection_id}{separator}{message_thread_id}{separator}{chat_id}{separator}{user_id}: {'state': ..., 'data': {}}
|
||||
|
||||
This function will help you to migrate from the old format to the new one in order to avoid data loss.
|
||||
|
||||
:param bot_id: Bot ID; To get it, call a getMe request and grab the id from the response.
|
||||
:type bot_id: int
|
||||
|
||||
:param prefix: Prefix for keys, default is "telebot_"(old default value)
|
||||
:type prefix: Optional[str]
|
||||
"""
|
||||
keys = self.redis.keys(f"{prefix}*")
|
||||
|
||||
for key in keys:
|
||||
old_key = key.decode("utf-8")
|
||||
# old: {prefix}chat_id: {user_id: {'state': None, 'data': {}}, ...}
|
||||
value = self.redis.get(old_key)
|
||||
value = json.loads(value)
|
||||
|
||||
chat_id = old_key[len(prefix) :]
|
||||
user_id = list(value.keys())[0]
|
||||
state = value[user_id]["state"]
|
||||
state_data = value[user_id]["data"]
|
||||
|
||||
# set new format
|
||||
new_key = self._get_key(
|
||||
int(chat_id), int(user_id), self.prefix, self.separator, bot_id=bot_id
|
||||
)
|
||||
self.redis.hset(new_key, "state", state)
|
||||
self.redis.hset(new_key, "data", json.dumps(state_data))
|
||||
|
||||
# delete old key
|
||||
self.redis.delete(old_key)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"StateRedisStorage({self.redis})"
|
||||
Reference in New Issue
Block a user