styledbots 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- styledbots/__init__.py +38 -0
- styledbots/core/__init__.py +4 -0
- styledbots/core/api.py +184 -0
- styledbots/core/bot.py +270 -0
- styledbots/core/exceptions.py +26 -0
- styledbots/core/types.py +107 -0
- styledbots/filters/__init__.py +1 -0
- styledbots/filters/filters.py +88 -0
- styledbots/handlers/__init__.py +2 -0
- styledbots/keyboards/__init__.py +3 -0
- styledbots/keyboards/builder.py +28 -0
- styledbots/keyboards/inline.py +98 -0
- styledbots/keyboards/reply.py +75 -0
- styledbots/storage/__init__.py +3 -0
- styledbots/storage/base.py +23 -0
- styledbots/storage/file.py +46 -0
- styledbots/storage/memory.py +27 -0
- styledbots/styles/__init__.py +3 -0
- styledbots/styles/formatter.py +40 -0
- styledbots/styles/style.py +57 -0
- styledbots/styles/templates.py +36 -0
- styledbots/utils/__init__.py +1 -0
- styledbots/utils/helpers.py +26 -0
- styledbots/version.py +3 -0
- styledbots-1.0.0.dist-info/METADATA +144 -0
- styledbots-1.0.0.dist-info/RECORD +29 -0
- styledbots-1.0.0.dist-info/WHEEL +5 -0
- styledbots-1.0.0.dist-info/licenses/LICENSE +21 -0
- styledbots-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Callable, List, Union, Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class filters:
|
|
6
|
+
"""Filter factory - Telebot style + more power"""
|
|
7
|
+
|
|
8
|
+
@staticmethod
|
|
9
|
+
def text(value: str = None) -> Callable:
|
|
10
|
+
def f(message) -> bool:
|
|
11
|
+
if not message.text:
|
|
12
|
+
return False
|
|
13
|
+
if value is None:
|
|
14
|
+
return True
|
|
15
|
+
return message.text == value
|
|
16
|
+
return f
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def command(commands: Union[str, List[str]]) -> Callable:
|
|
20
|
+
if isinstance(commands, str):
|
|
21
|
+
commands = [commands]
|
|
22
|
+
commands = [c.lower().lstrip("/") for c in commands]
|
|
23
|
+
|
|
24
|
+
def f(message) -> bool:
|
|
25
|
+
if not message.text or not message.text.startswith("/"):
|
|
26
|
+
return False
|
|
27
|
+
cmd = message.text.split()[0][1:].split("@")[0].lower()
|
|
28
|
+
return cmd in commands
|
|
29
|
+
return f
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def regexp(pattern: str, flags=0) -> Callable:
|
|
33
|
+
compiled = re.compile(pattern, flags)
|
|
34
|
+
|
|
35
|
+
def f(message) -> bool:
|
|
36
|
+
if not message.text:
|
|
37
|
+
return False
|
|
38
|
+
return bool(compiled.search(message.text))
|
|
39
|
+
return f
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def content_type(types: Union[str, List[str]]) -> Callable:
|
|
43
|
+
if isinstance(types, str):
|
|
44
|
+
types = [types]
|
|
45
|
+
|
|
46
|
+
def f(message) -> bool:
|
|
47
|
+
return message.content_type in types
|
|
48
|
+
return f
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def chat_type(types: Union[str, List[str]]) -> Callable:
|
|
52
|
+
if isinstance(types, str):
|
|
53
|
+
types = [types]
|
|
54
|
+
|
|
55
|
+
def f(message) -> bool:
|
|
56
|
+
return message.chat.type in types
|
|
57
|
+
return f
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def private() -> Callable:
|
|
61
|
+
return filters.chat_type("private")
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def group() -> Callable:
|
|
65
|
+
return filters.chat_type(["group", "supergroup"])
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def func(function: Callable) -> Callable:
|
|
69
|
+
"""Custom filter function"""
|
|
70
|
+
return function
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def user(user_ids: Union[int, List[int]]) -> Callable:
|
|
74
|
+
if isinstance(user_ids, int):
|
|
75
|
+
user_ids = [user_ids]
|
|
76
|
+
|
|
77
|
+
def f(message) -> bool:
|
|
78
|
+
return message.from_user and message.from_user.id in user_ids
|
|
79
|
+
return f
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def chat(chat_ids: Union[int, List[int]]) -> Callable:
|
|
83
|
+
if isinstance(chat_ids, int):
|
|
84
|
+
chat_ids = [chat_ids]
|
|
85
|
+
|
|
86
|
+
def f(message) -> bool:
|
|
87
|
+
return message.chat.id in chat_ids
|
|
88
|
+
return f
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .inline import InlineKeyboard, InlineButton
|
|
2
|
+
from .reply import ReplyKeyboard
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class KeyboardBuilder:
|
|
6
|
+
"""
|
|
7
|
+
Super simple keyboard factory
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def inline() -> InlineKeyboard:
|
|
12
|
+
return InlineKeyboard()
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def reply(resize: bool = True, one_time: bool = False) -> ReplyKeyboard:
|
|
16
|
+
return ReplyKeyboard(resize_keyboard=resize, one_time_keyboard=one_time)
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def yes_no(yes_data: str = "yes", no_data: str = "no") -> InlineKeyboard:
|
|
20
|
+
kb = InlineKeyboard()
|
|
21
|
+
kb.button("â
Yes", yes_data).button("â No", no_data)
|
|
22
|
+
return kb
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def confirm_cancel(confirm_data: str = "confirm", cancel_data: str = "cancel") -> InlineKeyboard:
|
|
26
|
+
kb = InlineKeyboard()
|
|
27
|
+
kb.button("â
Confirm", confirm_data).button("đĢ Cancel", cancel_data)
|
|
28
|
+
return kb
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from typing import List, Optional, Union
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class InlineButton:
|
|
5
|
+
def __init__(self, text: str, callback_data: str = None, url: str = None,
|
|
6
|
+
switch_inline_query: str = None, switch_inline_query_current_chat: str = None):
|
|
7
|
+
self.text = text
|
|
8
|
+
self.callback_data = callback_data
|
|
9
|
+
self.url = url
|
|
10
|
+
self.switch_inline_query = switch_inline_query
|
|
11
|
+
self.switch_inline_query_current_chat = switch_inline_query_current_chat
|
|
12
|
+
|
|
13
|
+
def to_dict(self) -> dict:
|
|
14
|
+
data = {"text": self.text}
|
|
15
|
+
if self.callback_data is not None:
|
|
16
|
+
data["callback_data"] = str(self.callback_data)
|
|
17
|
+
if self.url:
|
|
18
|
+
data["url"] = self.url
|
|
19
|
+
if self.switch_inline_query is not None:
|
|
20
|
+
data["switch_inline_query"] = self.switch_inline_query
|
|
21
|
+
if self.switch_inline_query_current_chat is not None:
|
|
22
|
+
data["switch_inline_query_current_chat"] = self.switch_inline_query_current_chat
|
|
23
|
+
return data
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InlineKeyboard:
|
|
27
|
+
"""
|
|
28
|
+
Fluent Inline Keyboard builder
|
|
29
|
+
Example:
|
|
30
|
+
kb = InlineKeyboard()
|
|
31
|
+
kb.button("Yes", "yes").button("No", "no")
|
|
32
|
+
kb.row()
|
|
33
|
+
kb.button("Cancel", "cancel")
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.keyboard: List[List[dict]] = []
|
|
38
|
+
self._current_row: List[dict] = []
|
|
39
|
+
|
|
40
|
+
def button(self, text: str, callback_data: str = None, url: str = None, **kwargs):
|
|
41
|
+
btn = InlineButton(text, callback_data=callback_data, url=url, **kwargs)
|
|
42
|
+
self._current_row.append(btn.to_dict())
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def add(self, *buttons: InlineButton):
|
|
46
|
+
"""Add multiple buttons to current row"""
|
|
47
|
+
for btn in buttons:
|
|
48
|
+
self._current_row.append(btn.to_dict() if isinstance(btn, InlineButton) else btn)
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def row(self, *buttons):
|
|
52
|
+
"""Finish current row and optionally start new one with buttons"""
|
|
53
|
+
if self._current_row:
|
|
54
|
+
self.keyboard.append(self._current_row)
|
|
55
|
+
self._current_row = []
|
|
56
|
+
if buttons:
|
|
57
|
+
for btn in buttons:
|
|
58
|
+
if isinstance(btn, InlineButton):
|
|
59
|
+
self._current_row.append(btn.to_dict())
|
|
60
|
+
elif isinstance(btn, dict):
|
|
61
|
+
self._current_row.append(btn)
|
|
62
|
+
else:
|
|
63
|
+
# assume (text, callback)
|
|
64
|
+
self._current_row.append(InlineButton(btn[0], btn[1]).to_dict())
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def adjust(self, *sizes: int):
|
|
68
|
+
"""
|
|
69
|
+
Automatically arrange buttons in rows of given sizes.
|
|
70
|
+
Example: kb.adjust(2, 1, 3)
|
|
71
|
+
"""
|
|
72
|
+
all_buttons = []
|
|
73
|
+
for row in self.keyboard:
|
|
74
|
+
all_buttons.extend(row)
|
|
75
|
+
if self._current_row:
|
|
76
|
+
all_buttons.extend(self._current_row)
|
|
77
|
+
|
|
78
|
+
self.keyboard = []
|
|
79
|
+
self._current_row = []
|
|
80
|
+
idx = 0
|
|
81
|
+
for size in sizes:
|
|
82
|
+
row = all_buttons[idx:idx + size]
|
|
83
|
+
if row:
|
|
84
|
+
self.keyboard.append(row)
|
|
85
|
+
idx += size
|
|
86
|
+
# remaining
|
|
87
|
+
if idx < len(all_buttons):
|
|
88
|
+
self.keyboard.append(all_buttons[idx:])
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def to_dict(self) -> dict:
|
|
92
|
+
if self._current_row:
|
|
93
|
+
self.keyboard.append(self._current_row)
|
|
94
|
+
self._current_row = []
|
|
95
|
+
return {"inline_keyboard": self.keyboard}
|
|
96
|
+
|
|
97
|
+
def __repr__(self):
|
|
98
|
+
return f"<InlineKeyboard rows={len(self.keyboard)}>"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ReplyButton:
|
|
5
|
+
def __init__(self, text: str, request_contact: bool = False, request_location: bool = False):
|
|
6
|
+
self.text = text
|
|
7
|
+
self.request_contact = request_contact
|
|
8
|
+
self.request_location = request_location
|
|
9
|
+
|
|
10
|
+
def to_dict(self) -> dict:
|
|
11
|
+
data = {"text": self.text}
|
|
12
|
+
if self.request_contact:
|
|
13
|
+
data["request_contact"] = True
|
|
14
|
+
if self.request_location:
|
|
15
|
+
data["request_location"] = True
|
|
16
|
+
return data
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ReplyKeyboard:
|
|
20
|
+
"""
|
|
21
|
+
Reply Keyboard builder
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, resize_keyboard: bool = True, one_time_keyboard: bool = False,
|
|
25
|
+
input_field_placeholder: str = None, selective: bool = False):
|
|
26
|
+
self.keyboard: List[List[dict]] = []
|
|
27
|
+
self._current_row: List[dict] = []
|
|
28
|
+
self.resize_keyboard = resize_keyboard
|
|
29
|
+
self.one_time_keyboard = one_time_keyboard
|
|
30
|
+
self.input_field_placeholder = input_field_placeholder
|
|
31
|
+
self.selective = selective
|
|
32
|
+
|
|
33
|
+
def button(self, text: str, request_contact: bool = False, request_location: bool = False):
|
|
34
|
+
btn = ReplyButton(text, request_contact, request_location)
|
|
35
|
+
self._current_row.append(btn.to_dict())
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
def row(self, *texts: str):
|
|
39
|
+
if self._current_row:
|
|
40
|
+
self.keyboard.append(self._current_row)
|
|
41
|
+
self._current_row = []
|
|
42
|
+
if texts:
|
|
43
|
+
self._current_row = [{"text": t} for t in texts]
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def add(self, *texts: str):
|
|
47
|
+
for t in texts:
|
|
48
|
+
self._current_row.append({"text": t})
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> dict:
|
|
52
|
+
if self._current_row:
|
|
53
|
+
self.keyboard.append(self._current_row)
|
|
54
|
+
self._current_row = []
|
|
55
|
+
|
|
56
|
+
data = {
|
|
57
|
+
"keyboard": self.keyboard,
|
|
58
|
+
"resize_keyboard": self.resize_keyboard,
|
|
59
|
+
"one_time_keyboard": self.one_time_keyboard,
|
|
60
|
+
"selective": self.selective
|
|
61
|
+
}
|
|
62
|
+
if self.input_field_placeholder:
|
|
63
|
+
data["input_field_placeholder"] = self.input_field_placeholder
|
|
64
|
+
return data
|
|
65
|
+
|
|
66
|
+
def __repr__(self):
|
|
67
|
+
return f"<ReplyKeyboard rows={len(self.keyboard)}>"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ReplyKeyboardRemove:
|
|
71
|
+
def __init__(self, selective: bool = False):
|
|
72
|
+
self.selective = selective
|
|
73
|
+
|
|
74
|
+
def to_dict(self) -> dict:
|
|
75
|
+
return {"remove_keyboard": True, "selective": self.selective}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaseStorage(ABC):
|
|
6
|
+
@abstractmethod
|
|
7
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def set(self, key: str, value: Any) -> None:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def delete(self, key: str) -> None:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def exists(self, key: str) -> bool:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def clear(self) -> None:
|
|
23
|
+
pass
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any
|
|
4
|
+
from .base import BaseStorage
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FileStorage(BaseStorage):
|
|
8
|
+
"""Simple JSON file storage (persistent)"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, filename: str = "styledbots_data.json"):
|
|
11
|
+
self.filename = filename
|
|
12
|
+
self._data = {}
|
|
13
|
+
self._load()
|
|
14
|
+
|
|
15
|
+
def _load(self):
|
|
16
|
+
if os.path.exists(self.filename):
|
|
17
|
+
try:
|
|
18
|
+
with open(self.filename, "r", encoding="utf-8") as f:
|
|
19
|
+
self._data = json.load(f)
|
|
20
|
+
except Exception:
|
|
21
|
+
self._data = {}
|
|
22
|
+
else:
|
|
23
|
+
self._data = {}
|
|
24
|
+
|
|
25
|
+
def _save(self):
|
|
26
|
+
with open(self.filename, "w", encoding="utf-8") as f:
|
|
27
|
+
json.dump(self._data, f, ensure_ascii=False, indent=2)
|
|
28
|
+
|
|
29
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
30
|
+
return self._data.get(key, default)
|
|
31
|
+
|
|
32
|
+
def set(self, key: str, value: Any) -> None:
|
|
33
|
+
self._data[key] = value
|
|
34
|
+
self._save()
|
|
35
|
+
|
|
36
|
+
def delete(self, key: str) -> None:
|
|
37
|
+
if key in self._data:
|
|
38
|
+
del self._data[key]
|
|
39
|
+
self._save()
|
|
40
|
+
|
|
41
|
+
def exists(self, key: str) -> bool:
|
|
42
|
+
return key in self._data
|
|
43
|
+
|
|
44
|
+
def clear(self) -> None:
|
|
45
|
+
self._data.clear()
|
|
46
|
+
self._save()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
from .base import BaseStorage
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MemoryStorage(BaseStorage):
|
|
6
|
+
"""Simple in-memory storage (fast, lost on restart)"""
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self._data: Dict[str, Any] = {}
|
|
10
|
+
|
|
11
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
12
|
+
return self._data.get(key, default)
|
|
13
|
+
|
|
14
|
+
def set(self, key: str, value: Any) -> None:
|
|
15
|
+
self._data[key] = value
|
|
16
|
+
|
|
17
|
+
def delete(self, key: str) -> None:
|
|
18
|
+
self._data.pop(key, None)
|
|
19
|
+
|
|
20
|
+
def exists(self, key: str) -> bool:
|
|
21
|
+
return key in self._data
|
|
22
|
+
|
|
23
|
+
def clear(self) -> None:
|
|
24
|
+
self._data.clear()
|
|
25
|
+
|
|
26
|
+
def all(self) -> Dict[str, Any]:
|
|
27
|
+
return self._data.copy()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import html
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def escape_html(text: str) -> str:
|
|
6
|
+
"""Escape special HTML characters"""
|
|
7
|
+
return html.escape(str(text))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def format_text(
|
|
11
|
+
text: str,
|
|
12
|
+
bold: bool = False,
|
|
13
|
+
italic: bool = False,
|
|
14
|
+
underline: bool = False,
|
|
15
|
+
strike: bool = False,
|
|
16
|
+
code: bool = False,
|
|
17
|
+
spoiler: bool = False,
|
|
18
|
+
link: Optional[str] = None
|
|
19
|
+
) -> str:
|
|
20
|
+
"""
|
|
21
|
+
Quickly format a text with multiple styles
|
|
22
|
+
"""
|
|
23
|
+
result = escape_html(text)
|
|
24
|
+
|
|
25
|
+
if code:
|
|
26
|
+
result = f"<code>{result}</code>"
|
|
27
|
+
if bold:
|
|
28
|
+
result = f"<b>{result}</b>"
|
|
29
|
+
if italic:
|
|
30
|
+
result = f"<i>{result}</i>"
|
|
31
|
+
if underline:
|
|
32
|
+
result = f"<u>{result}</u>"
|
|
33
|
+
if strike:
|
|
34
|
+
result = f"<s>{result}</s>"
|
|
35
|
+
if spoiler:
|
|
36
|
+
result = f"<tg-spoiler>{result}</tg-spoiler>"
|
|
37
|
+
if link:
|
|
38
|
+
result = f'<a href="{link}">{result}</a>'
|
|
39
|
+
|
|
40
|
+
return result
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class Style:
|
|
2
|
+
"""
|
|
3
|
+
Beautiful message styling helpers.
|
|
4
|
+
Use with parse_mode="HTML" (default in StyledBots)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def bold(text: str) -> str:
|
|
9
|
+
return f"<b>{text}</b>"
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def italic(text: str) -> str:
|
|
13
|
+
return f"<i>{text}</i>"
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def underline(text: str) -> str:
|
|
17
|
+
return f"<u>{text}</u>"
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def strike(text: str) -> str:
|
|
21
|
+
return f"<s>{text}</s>"
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def code(text: str) -> str:
|
|
25
|
+
return f"<code>{text}</code>"
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def pre(text: str, language: str = "") -> str:
|
|
29
|
+
if language:
|
|
30
|
+
return f'<pre><code class="language-{language}">{text}</code></pre>'
|
|
31
|
+
return f"<pre>{text}</pre>"
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def spoiler(text: str) -> str:
|
|
35
|
+
return f"<tg-spoiler>{text}</tg-spoiler>"
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def link(text: str, url: str) -> str:
|
|
39
|
+
return f'<a href="{url}">{text}</a>'
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def mention(user_id: int, name: str) -> str:
|
|
43
|
+
return f'<a href="tg://user?id={user_id}">{name}</a>'
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def quote(text: str) -> str:
|
|
47
|
+
return f"<blockquote>{text}</blockquote>"
|
|
48
|
+
|
|
49
|
+
# Shortcuts
|
|
50
|
+
b = bold
|
|
51
|
+
i = italic
|
|
52
|
+
u = underline
|
|
53
|
+
s = strike
|
|
54
|
+
c = code
|
|
55
|
+
p = pre
|
|
56
|
+
sp = spoiler
|
|
57
|
+
a = link
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from .style import Style
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Templates:
|
|
5
|
+
"""Pre-made beautiful message templates"""
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def welcome(name: str) -> str:
|
|
9
|
+
return (
|
|
10
|
+
f"đ Hello {Style.bold(name)}!\n\n"
|
|
11
|
+
f"Welcome to the bot.\n"
|
|
12
|
+
f"Use /help to see available commands."
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def error(message: str = "Something went wrong") -> str:
|
|
17
|
+
return f"â {Style.bold('Error')}\n\n{message}"
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def success(message: str) -> str:
|
|
21
|
+
return f"â
{Style.bold('Success')}\n\n{message}"
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def info(title: str, body: str) -> str:
|
|
25
|
+
return f"âšī¸ {Style.bold(title)}\n\n{body}"
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def warning(message: str) -> str:
|
|
29
|
+
return f"â ī¸ {Style.bold('Warning')}\n\n{message}"
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def code_block(title: str, code: str, language: str = "python") -> str:
|
|
33
|
+
return (
|
|
34
|
+
f"{Style.bold(title)}\n"
|
|
35
|
+
f"{Style.pre(code, language)}"
|
|
36
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .helpers import *
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def extract_args(text: str) -> list:
|
|
5
|
+
"""Extract arguments from command text"""
|
|
6
|
+
if not text:
|
|
7
|
+
return []
|
|
8
|
+
parts = text.split(maxsplit=1)
|
|
9
|
+
if len(parts) < 2:
|
|
10
|
+
return []
|
|
11
|
+
return parts[1].split()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def extract_command(text: str) -> Optional[str]:
|
|
15
|
+
"""Extract command name from text"""
|
|
16
|
+
if not text or not text.startswith("/"):
|
|
17
|
+
return None
|
|
18
|
+
return text.split()[0][1:].split("@")[0].lower()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def mention_html(user_id: int, name: str) -> str:
|
|
22
|
+
return f'<a href="tg://user?id={user_id}">{name}</a>'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def mention_markdown(user_id: int, name: str) -> str:
|
|
26
|
+
return f"[{name}](tg://user?id={user_id})"
|
styledbots/version.py
ADDED