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.
@@ -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,2 @@
1
+ # Handlers are integrated into Bot class for simplicity
2
+ # This module is reserved for future advanced handler systems
@@ -0,0 +1,3 @@
1
+ from .inline import InlineKeyboard, InlineButton
2
+ from .reply import ReplyKeyboard, ReplyButton
3
+ from .builder import KeyboardBuilder
@@ -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,3 @@
1
+ from .base import BaseStorage
2
+ from .memory import MemoryStorage
3
+ from .file import FileStorage
@@ -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,3 @@
1
+ from .style import Style
2
+ from .formatter import format_text, escape_html
3
+ from .templates import Templates
@@ -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
@@ -0,0 +1,3 @@
1
+ __version__ = "1.0.0"
2
+ __author__ = "StyledBots Team"
3
+ __description__ = "Modern Telegram Bot library - Better than Telebot"