kformat-telebot 0.1.0__tar.gz
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.
- kformat_telebot-0.1.0/.gitignore +6 -0
- kformat_telebot-0.1.0/LICENSE +17 -0
- kformat_telebot-0.1.0/PKG-INFO +126 -0
- kformat_telebot-0.1.0/README.md +96 -0
- kformat_telebot-0.1.0/kformat.py +144 -0
- kformat_telebot-0.1.0/pyproject.toml +39 -0
- kformat_telebot-0.1.0/src/kformat/__init__.py +144 -0
- kformat_telebot-0.1.0/src/kformat/py.typed +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KFormat contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: kformat-telebot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Short Telegram HTML formatting, tables, inline keyboards, and Rich Message JSON helpers for pyTelegramBotAPI.
|
|
5
|
+
Project-URL: Homepage, https://pypi.org/project/kformat-telebot/
|
|
6
|
+
Project-URL: Repository, https://github.com/YOUR_USERNAME/kformat-telebot
|
|
7
|
+
Author: KFormat contributors
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: bot,formatting,pytelegrambotapi,rich-message,telebot,telegram
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Communications :: Chat
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
26
|
+
Requires-Dist: twine>=5.1; extra == 'dev'
|
|
27
|
+
Provides-Extra: telebot
|
|
28
|
+
Requires-Dist: pytelegrambotapi>=4.14; extra == 'telebot'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# KFormat
|
|
32
|
+
|
|
33
|
+
Short aliases for Telegram HTML formatting, tables, inline keyboards, and Rich Message JSON helpers.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install kformat-telebot
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For inline keyboard helpers, install the optional TeleBot dependency:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install "kformat-telebot[telebot]"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick example
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import telebot
|
|
51
|
+
from kformat import K
|
|
52
|
+
|
|
53
|
+
bot = telebot.TeleBot("YOUR_BOT_TOKEN")
|
|
54
|
+
|
|
55
|
+
@bot.message_handler(commands=["start"])
|
|
56
|
+
def start(message):
|
|
57
|
+
text = (
|
|
58
|
+
K.b("Bold") + "\n"
|
|
59
|
+
+ K.un("Underline") + "\n"
|
|
60
|
+
+ K.sp("Spoiler") + "\n"
|
|
61
|
+
+ K.a("Open website", "https://example.com")
|
|
62
|
+
)
|
|
63
|
+
bot.send_message(message.chat.id, text, parse_mode="HTML")
|
|
64
|
+
|
|
65
|
+
bot.infinity_polling()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Tables
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
text = K.table(
|
|
72
|
+
[["Kentucky", "10"], ["Kentucky", "20"]],
|
|
73
|
+
headers=["Name", "Count"]
|
|
74
|
+
)
|
|
75
|
+
bot.send_message(chat_id, text, parse_mode="HTML")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Classic Telegram `sendMessage` has no native table-cell layout, so `K.table` renders
|
|
79
|
+
a readable monospace text table.
|
|
80
|
+
|
|
81
|
+
## Inline keyboard
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
markup = K.buttons(
|
|
85
|
+
[("Open", "callback:open"), ("Website", "url:https://example.com")],
|
|
86
|
+
[K.btn("Second row", data="second")]
|
|
87
|
+
)
|
|
88
|
+
bot.send_message(chat_id, "Choose:", reply_markup=markup)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Rich Message JSON
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
payload = K.rich(
|
|
95
|
+
K.rh("Heading"),
|
|
96
|
+
K.rb("Paragraph"),
|
|
97
|
+
K.rtable([["Name", "Count"], ["Kentucky", "10"]])
|
|
98
|
+
)
|
|
99
|
+
print(K.rich_json(K.rh("Heading"), K.rb("Paragraph")))
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Rich Message helpers only build the JSON structure. Sending it requires a Telegram
|
|
103
|
+
Bot API method/client that supports Rich Messages.
|
|
104
|
+
|
|
105
|
+
## API
|
|
106
|
+
|
|
107
|
+
- `K.b(text)`: bold
|
|
108
|
+
- `K.i(text)`: italic
|
|
109
|
+
- `K.un(text)`: underline
|
|
110
|
+
- `K.st(text)`: strikethrough
|
|
111
|
+
- `K.sp(text)`: spoiler
|
|
112
|
+
- `K.c(text)`: inline code
|
|
113
|
+
- `K.pre(text, lang=None)`: preformatted code
|
|
114
|
+
- `K.a(text, url)`: hyperlink
|
|
115
|
+
- `K.tg(text, user_id)`: user mention
|
|
116
|
+
- `K.block(text)`: blockquote
|
|
117
|
+
- `K.expand(text)`: expandable blockquote
|
|
118
|
+
- `K.table(rows, headers=None)`: monospace table
|
|
119
|
+
- `K.buttons(*rows)`: inline keyboard markup
|
|
120
|
+
- `K.btn(text, data=... or url=...)`: button specification
|
|
121
|
+
- `K.rb`, `K.rh`, `K.rdiv`, `K.rtable`, `K.rbutton`, `K.rbuttons`, `K.rich`, `K.rich_json`: Rich Message JSON builders
|
|
122
|
+
- `K.ul`, `K.ol`, `K.bullet`, `K.hr`, `K.join`, `K.plain`, `K.raw`
|
|
123
|
+
- `K.help()`: usage guide
|
|
124
|
+
|
|
125
|
+
Text helpers return Telegram HTML fragments. Use `parse_mode="HTML"`.
|
|
126
|
+
Text is escaped automatically. `K.raw()` is for trusted HTML only.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# KFormat
|
|
2
|
+
|
|
3
|
+
Short aliases for Telegram HTML formatting, tables, inline keyboards, and Rich Message JSON helpers.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install kformat-telebot
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For inline keyboard helpers, install the optional TeleBot dependency:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install "kformat-telebot[telebot]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick example
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import telebot
|
|
21
|
+
from kformat import K
|
|
22
|
+
|
|
23
|
+
bot = telebot.TeleBot("YOUR_BOT_TOKEN")
|
|
24
|
+
|
|
25
|
+
@bot.message_handler(commands=["start"])
|
|
26
|
+
def start(message):
|
|
27
|
+
text = (
|
|
28
|
+
K.b("Bold") + "\n"
|
|
29
|
+
+ K.un("Underline") + "\n"
|
|
30
|
+
+ K.sp("Spoiler") + "\n"
|
|
31
|
+
+ K.a("Open website", "https://example.com")
|
|
32
|
+
)
|
|
33
|
+
bot.send_message(message.chat.id, text, parse_mode="HTML")
|
|
34
|
+
|
|
35
|
+
bot.infinity_polling()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Tables
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
text = K.table(
|
|
42
|
+
[["Kentucky", "10"], ["Kentucky", "20"]],
|
|
43
|
+
headers=["Name", "Count"]
|
|
44
|
+
)
|
|
45
|
+
bot.send_message(chat_id, text, parse_mode="HTML")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Classic Telegram `sendMessage` has no native table-cell layout, so `K.table` renders
|
|
49
|
+
a readable monospace text table.
|
|
50
|
+
|
|
51
|
+
## Inline keyboard
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
markup = K.buttons(
|
|
55
|
+
[("Open", "callback:open"), ("Website", "url:https://example.com")],
|
|
56
|
+
[K.btn("Second row", data="second")]
|
|
57
|
+
)
|
|
58
|
+
bot.send_message(chat_id, "Choose:", reply_markup=markup)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Rich Message JSON
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
payload = K.rich(
|
|
65
|
+
K.rh("Heading"),
|
|
66
|
+
K.rb("Paragraph"),
|
|
67
|
+
K.rtable([["Name", "Count"], ["Kentucky", "10"]])
|
|
68
|
+
)
|
|
69
|
+
print(K.rich_json(K.rh("Heading"), K.rb("Paragraph")))
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Rich Message helpers only build the JSON structure. Sending it requires a Telegram
|
|
73
|
+
Bot API method/client that supports Rich Messages.
|
|
74
|
+
|
|
75
|
+
## API
|
|
76
|
+
|
|
77
|
+
- `K.b(text)`: bold
|
|
78
|
+
- `K.i(text)`: italic
|
|
79
|
+
- `K.un(text)`: underline
|
|
80
|
+
- `K.st(text)`: strikethrough
|
|
81
|
+
- `K.sp(text)`: spoiler
|
|
82
|
+
- `K.c(text)`: inline code
|
|
83
|
+
- `K.pre(text, lang=None)`: preformatted code
|
|
84
|
+
- `K.a(text, url)`: hyperlink
|
|
85
|
+
- `K.tg(text, user_id)`: user mention
|
|
86
|
+
- `K.block(text)`: blockquote
|
|
87
|
+
- `K.expand(text)`: expandable blockquote
|
|
88
|
+
- `K.table(rows, headers=None)`: monospace table
|
|
89
|
+
- `K.buttons(*rows)`: inline keyboard markup
|
|
90
|
+
- `K.btn(text, data=... or url=...)`: button specification
|
|
91
|
+
- `K.rb`, `K.rh`, `K.rdiv`, `K.rtable`, `K.rbutton`, `K.rbuttons`, `K.rich`, `K.rich_json`: Rich Message JSON builders
|
|
92
|
+
- `K.ul`, `K.ol`, `K.bullet`, `K.hr`, `K.join`, `K.plain`, `K.raw`
|
|
93
|
+
- `K.help()`: usage guide
|
|
94
|
+
|
|
95
|
+
Text helpers return Telegram HTML fragments. Use `parse_mode="HTML"`.
|
|
96
|
+
Text is escaped automatically. `K.raw()` is for trusted HTML only.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""KFormat: short Telegram HTML and keyboard helpers for pyTelegramBotAPI."""
|
|
2
|
+
import html, json, re
|
|
3
|
+
|
|
4
|
+
def _e(x):
|
|
5
|
+
return html.escape(str(x), quote=False)
|
|
6
|
+
|
|
7
|
+
class K:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def b(x): return f"<b>{_e(x)}</b>"
|
|
10
|
+
@staticmethod
|
|
11
|
+
def i(x): return f"<i>{_e(x)}</i>"
|
|
12
|
+
@staticmethod
|
|
13
|
+
def un(x): return f"<u>{_e(x)}</u>"
|
|
14
|
+
@staticmethod
|
|
15
|
+
def st(x): return f"<s>{_e(x)}</s>"
|
|
16
|
+
@staticmethod
|
|
17
|
+
def sp(x): return f"<tg-spoiler>{_e(x)}</tg-spoiler>"
|
|
18
|
+
@staticmethod
|
|
19
|
+
def c(x): return f"<code>{_e(x)}</code>"
|
|
20
|
+
@staticmethod
|
|
21
|
+
def pre(x, lang=None):
|
|
22
|
+
body = _e(x)
|
|
23
|
+
if lang:
|
|
24
|
+
lang = re.sub(r"[^a-zA-Z0-9_+#.-]", "", str(lang))
|
|
25
|
+
return f'<pre><code class="language-{lang}">{body}</code></pre>'
|
|
26
|
+
return f"<pre>{body}</pre>"
|
|
27
|
+
@staticmethod
|
|
28
|
+
def a(text, url): return f'<a href="{html.escape(str(url), quote=True)}">{_e(text)}</a>'
|
|
29
|
+
@staticmethod
|
|
30
|
+
def tg(text, user_id): return f'<a href="tg://user?id={int(user_id)}">{_e(text)}</a>'
|
|
31
|
+
@staticmethod
|
|
32
|
+
def block(x): return f"<blockquote>{_e(x)}</blockquote>"
|
|
33
|
+
@staticmethod
|
|
34
|
+
def expand(x): return f"<blockquote expandable>{_e(x)}</blockquote>"
|
|
35
|
+
@staticmethod
|
|
36
|
+
def raw(x): return str(x) # trusted HTML only
|
|
37
|
+
@staticmethod
|
|
38
|
+
def plain(x): return _e(x)
|
|
39
|
+
@staticmethod
|
|
40
|
+
def ul(*items): return "\n".join("• " + _e(x) for x in items)
|
|
41
|
+
@staticmethod
|
|
42
|
+
def ol(*items): return "\n".join(f"{n}. " + _e(x) for n, x in enumerate(items, 1))
|
|
43
|
+
@staticmethod
|
|
44
|
+
def bullet(x): return "• " + _e(x)
|
|
45
|
+
@staticmethod
|
|
46
|
+
def hr(char="─", count=18): return _e((str(char) or "─")[0] * max(1, min(int(count), 100)))
|
|
47
|
+
@staticmethod
|
|
48
|
+
def join(*parts, sep=""): return sep.join(map(str, parts))
|
|
49
|
+
@staticmethod
|
|
50
|
+
def table(rows, headers=None, gap=2):
|
|
51
|
+
data = ([list(headers)] if headers is not None else []) + [list(r) for r in rows]
|
|
52
|
+
if not data: return "<pre></pre>"
|
|
53
|
+
n = max(map(len, data))
|
|
54
|
+
data = [[str(v).replace("\n", " ") for v in r] + [""]*(n-len(r)) for r in data]
|
|
55
|
+
widths = [max(len(r[i]) for r in data) for i in range(n)]
|
|
56
|
+
lines = []
|
|
57
|
+
for ri, row in enumerate(data):
|
|
58
|
+
lines.append((" "*gap).join(row[i].ljust(widths[i]) for i in range(n)).rstrip())
|
|
59
|
+
if headers is not None and ri == 0:
|
|
60
|
+
lines.append((" "*gap).join("-"*w for w in widths).rstrip())
|
|
61
|
+
return "<pre>" + _e("\n".join(lines)) + "</pre>"
|
|
62
|
+
@staticmethod
|
|
63
|
+
def btn(text, data=None, url=None):
|
|
64
|
+
if (data is None) == (url is None): raise ValueError("حدد data أو url فقط")
|
|
65
|
+
return {"text": str(text), "callback_data": str(data)} if data is not None else {"text": str(text), "url": str(url)}
|
|
66
|
+
@staticmethod
|
|
67
|
+
def buttons(*rows):
|
|
68
|
+
try:
|
|
69
|
+
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo
|
|
70
|
+
except ImportError as e:
|
|
71
|
+
raise RuntimeError("ثبت المكتبة: pip install pyTelegramBotAPI") from e
|
|
72
|
+
markup = InlineKeyboardMarkup()
|
|
73
|
+
for row in rows:
|
|
74
|
+
if not isinstance(row, (list, tuple)): row = [row]
|
|
75
|
+
built = []
|
|
76
|
+
for item in row:
|
|
77
|
+
if isinstance(item, dict):
|
|
78
|
+
spec = dict(item); label = spec.pop("text", spec.pop("label", "Button"))
|
|
79
|
+
built.append(InlineKeyboardButton(str(label), **spec))
|
|
80
|
+
elif isinstance(item, str):
|
|
81
|
+
built.append(InlineKeyboardButton(item, callback_data=item))
|
|
82
|
+
elif isinstance(item, (list, tuple)) and len(item) >= 2:
|
|
83
|
+
label, target = str(item[0]), str(item[1])
|
|
84
|
+
if target.startswith("url:"): built.append(InlineKeyboardButton(label, url=target[4:]))
|
|
85
|
+
elif target.startswith("webapp:"): built.append(InlineKeyboardButton(label, web_app=WebAppInfo(target[7:])))
|
|
86
|
+
else: built.append(InlineKeyboardButton(label, callback_data=target[9:] if target.startswith("callback:") else target))
|
|
87
|
+
else: raise ValueError("صيغة زر غير صحيحة")
|
|
88
|
+
if built: markup.row(*built)
|
|
89
|
+
return markup
|
|
90
|
+
# Builders for Telegram Rich Message JSON. These build data; they do not send it.
|
|
91
|
+
@staticmethod
|
|
92
|
+
def rb(text): return {"type":"paragraph", "text":str(text)}
|
|
93
|
+
@staticmethod
|
|
94
|
+
def rh(text, size=1): return {"type":"heading", "text":str(text), "size":max(1,min(6,int(size)))}
|
|
95
|
+
@staticmethod
|
|
96
|
+
def rdiv(): return {"type":"divider"}
|
|
97
|
+
@staticmethod
|
|
98
|
+
def rtable(rows, header=True, bordered=True, striped=False, compact=False):
|
|
99
|
+
cells = [[{"text":str(v), **({"is_header":True} if header and ri==0 else {})} for v in row] for ri,row in enumerate(rows)]
|
|
100
|
+
return {"type":"table","cells":cells,"is_bordered":bool(bordered),"is_striped":bool(striped),"is_compact":bool(compact)}
|
|
101
|
+
@staticmethod
|
|
102
|
+
def rbutton(text, **action):
|
|
103
|
+
if len(action)!=1: raise ValueError("زر Rich يحتاج إجراء واحد")
|
|
104
|
+
return {"text":str(text), **action}
|
|
105
|
+
@staticmethod
|
|
106
|
+
def rbuttons(*rows, align="center"):
|
|
107
|
+
return {"type":"buttons","buttons":[dict(x) for row in rows for x in row],"align":align if align in ("left","center","right") else "center"}
|
|
108
|
+
@staticmethod
|
|
109
|
+
def rich(*blocks, is_rtl=True): return {"is_rtl":bool(is_rtl),"blocks":list(blocks)}
|
|
110
|
+
@staticmethod
|
|
111
|
+
def rich_json(*blocks, is_rtl=True, indent=2):
|
|
112
|
+
return json.dumps({"rich_message":K.rich(*blocks,is_rtl=is_rtl)},ensure_ascii=False,indent=indent)
|
|
113
|
+
@staticmethod
|
|
114
|
+
def help():
|
|
115
|
+
return """KFormat guide
|
|
116
|
+
b(text) عريض
|
|
117
|
+
i(text) مائل
|
|
118
|
+
un(text) تسطير
|
|
119
|
+
st(text) مشطوب
|
|
120
|
+
sp(text) نص مخفي
|
|
121
|
+
c(text) كود داخل السطر
|
|
122
|
+
pre(text,lang) كتلة كود
|
|
123
|
+
a(text,url) رابط
|
|
124
|
+
tg(text,id) منشن بالآيدي
|
|
125
|
+
block(text) اقتباس
|
|
126
|
+
expand(text) اقتباس قابل للطي
|
|
127
|
+
table(rows, headers=[]) جدول نصي
|
|
128
|
+
buttons(*rows) أزرار Inline
|
|
129
|
+
btn(text,data=...) أو btn(text,url=...)
|
|
130
|
+
ul(*items), ol(*items), bullet(text), hr()
|
|
131
|
+
rb/rh/rdiv/rtable/rbutton/rbuttons/rich/rich_json لبناء Rich Message JSON
|
|
132
|
+
|
|
133
|
+
دوال النص تحتاج parse_mode='HTML'.
|
|
134
|
+
الجدول العادي نص monospace مرتب، مو خلايا أصلية في sendMessage.
|
|
135
|
+
دوال Rich تبني JSON فقط، ولا ترسله وحدها.
|
|
136
|
+
"""
|
|
137
|
+
bold=K.b
|
|
138
|
+
italic=K.i
|
|
139
|
+
underline=K.un
|
|
140
|
+
strike=K.st
|
|
141
|
+
spoiler=K.sp
|
|
142
|
+
code=K.c
|
|
143
|
+
table=K.table
|
|
144
|
+
buttons=K.buttons
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kformat-telebot"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Short Telegram HTML formatting, tables, inline keyboards, and Rich Message JSON helpers for pyTelegramBotAPI."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "KFormat contributors" }]
|
|
13
|
+
keywords = ["telegram", "telebot", "pytelegrambotapi", "formatting", "bot", "rich-message"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.8",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Communications :: Chat",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
27
|
+
]
|
|
28
|
+
dependencies = []
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
telebot = ["pyTelegramBotAPI>=4.14"]
|
|
32
|
+
dev = ["build>=1.2", "twine>=5.1"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://pypi.org/project/kformat-telebot/"
|
|
36
|
+
Repository = "https://github.com/YOUR_USERNAME/kformat-telebot"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["src/kformat"]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""KFormat: short Telegram HTML and keyboard helpers for pyTelegramBotAPI."""
|
|
2
|
+
import html, json, re
|
|
3
|
+
|
|
4
|
+
def _e(x):
|
|
5
|
+
return html.escape(str(x), quote=False)
|
|
6
|
+
|
|
7
|
+
class K:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def b(x): return f"<b>{_e(x)}</b>"
|
|
10
|
+
@staticmethod
|
|
11
|
+
def i(x): return f"<i>{_e(x)}</i>"
|
|
12
|
+
@staticmethod
|
|
13
|
+
def un(x): return f"<u>{_e(x)}</u>"
|
|
14
|
+
@staticmethod
|
|
15
|
+
def st(x): return f"<s>{_e(x)}</s>"
|
|
16
|
+
@staticmethod
|
|
17
|
+
def sp(x): return f"<tg-spoiler>{_e(x)}</tg-spoiler>"
|
|
18
|
+
@staticmethod
|
|
19
|
+
def c(x): return f"<code>{_e(x)}</code>"
|
|
20
|
+
@staticmethod
|
|
21
|
+
def pre(x, lang=None):
|
|
22
|
+
body = _e(x)
|
|
23
|
+
if lang:
|
|
24
|
+
lang = re.sub(r"[^a-zA-Z0-9_+#.-]", "", str(lang))
|
|
25
|
+
return f'<pre><code class="language-{lang}">{body}</code></pre>'
|
|
26
|
+
return f"<pre>{body}</pre>"
|
|
27
|
+
@staticmethod
|
|
28
|
+
def a(text, url): return f'<a href="{html.escape(str(url), quote=True)}">{_e(text)}</a>'
|
|
29
|
+
@staticmethod
|
|
30
|
+
def tg(text, user_id): return f'<a href="tg://user?id={int(user_id)}">{_e(text)}</a>'
|
|
31
|
+
@staticmethod
|
|
32
|
+
def block(x): return f"<blockquote>{_e(x)}</blockquote>"
|
|
33
|
+
@staticmethod
|
|
34
|
+
def expand(x): return f"<blockquote expandable>{_e(x)}</blockquote>"
|
|
35
|
+
@staticmethod
|
|
36
|
+
def raw(x): return str(x) # trusted HTML only
|
|
37
|
+
@staticmethod
|
|
38
|
+
def plain(x): return _e(x)
|
|
39
|
+
@staticmethod
|
|
40
|
+
def ul(*items): return "\n".join("• " + _e(x) for x in items)
|
|
41
|
+
@staticmethod
|
|
42
|
+
def ol(*items): return "\n".join(f"{n}. " + _e(x) for n, x in enumerate(items, 1))
|
|
43
|
+
@staticmethod
|
|
44
|
+
def bullet(x): return "• " + _e(x)
|
|
45
|
+
@staticmethod
|
|
46
|
+
def hr(char="─", count=18): return _e((str(char) or "─")[0] * max(1, min(int(count), 100)))
|
|
47
|
+
@staticmethod
|
|
48
|
+
def join(*parts, sep=""): return sep.join(map(str, parts))
|
|
49
|
+
@staticmethod
|
|
50
|
+
def table(rows, headers=None, gap=2):
|
|
51
|
+
data = ([list(headers)] if headers is not None else []) + [list(r) for r in rows]
|
|
52
|
+
if not data: return "<pre></pre>"
|
|
53
|
+
n = max(map(len, data))
|
|
54
|
+
data = [[str(v).replace("\n", " ") for v in r] + [""]*(n-len(r)) for r in data]
|
|
55
|
+
widths = [max(len(r[i]) for r in data) for i in range(n)]
|
|
56
|
+
lines = []
|
|
57
|
+
for ri, row in enumerate(data):
|
|
58
|
+
lines.append((" "*gap).join(row[i].ljust(widths[i]) for i in range(n)).rstrip())
|
|
59
|
+
if headers is not None and ri == 0:
|
|
60
|
+
lines.append((" "*gap).join("-"*w for w in widths).rstrip())
|
|
61
|
+
return "<pre>" + _e("\n".join(lines)) + "</pre>"
|
|
62
|
+
@staticmethod
|
|
63
|
+
def btn(text, data=None, url=None):
|
|
64
|
+
if (data is None) == (url is None): raise ValueError("حدد data أو url فقط")
|
|
65
|
+
return {"text": str(text), "callback_data": str(data)} if data is not None else {"text": str(text), "url": str(url)}
|
|
66
|
+
@staticmethod
|
|
67
|
+
def buttons(*rows):
|
|
68
|
+
try:
|
|
69
|
+
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo
|
|
70
|
+
except ImportError as e:
|
|
71
|
+
raise RuntimeError("ثبت المكتبة: pip install pyTelegramBotAPI") from e
|
|
72
|
+
markup = InlineKeyboardMarkup()
|
|
73
|
+
for row in rows:
|
|
74
|
+
if not isinstance(row, (list, tuple)): row = [row]
|
|
75
|
+
built = []
|
|
76
|
+
for item in row:
|
|
77
|
+
if isinstance(item, dict):
|
|
78
|
+
spec = dict(item); label = spec.pop("text", spec.pop("label", "Button"))
|
|
79
|
+
built.append(InlineKeyboardButton(str(label), **spec))
|
|
80
|
+
elif isinstance(item, str):
|
|
81
|
+
built.append(InlineKeyboardButton(item, callback_data=item))
|
|
82
|
+
elif isinstance(item, (list, tuple)) and len(item) >= 2:
|
|
83
|
+
label, target = str(item[0]), str(item[1])
|
|
84
|
+
if target.startswith("url:"): built.append(InlineKeyboardButton(label, url=target[4:]))
|
|
85
|
+
elif target.startswith("webapp:"): built.append(InlineKeyboardButton(label, web_app=WebAppInfo(target[7:])))
|
|
86
|
+
else: built.append(InlineKeyboardButton(label, callback_data=target[9:] if target.startswith("callback:") else target))
|
|
87
|
+
else: raise ValueError("صيغة زر غير صحيحة")
|
|
88
|
+
if built: markup.row(*built)
|
|
89
|
+
return markup
|
|
90
|
+
# Builders for Telegram Rich Message JSON. These build data; they do not send it.
|
|
91
|
+
@staticmethod
|
|
92
|
+
def rb(text): return {"type":"paragraph", "text":str(text)}
|
|
93
|
+
@staticmethod
|
|
94
|
+
def rh(text, size=1): return {"type":"heading", "text":str(text), "size":max(1,min(6,int(size)))}
|
|
95
|
+
@staticmethod
|
|
96
|
+
def rdiv(): return {"type":"divider"}
|
|
97
|
+
@staticmethod
|
|
98
|
+
def rtable(rows, header=True, bordered=True, striped=False, compact=False):
|
|
99
|
+
cells = [[{"text":str(v), **({"is_header":True} if header and ri==0 else {})} for v in row] for ri,row in enumerate(rows)]
|
|
100
|
+
return {"type":"table","cells":cells,"is_bordered":bool(bordered),"is_striped":bool(striped),"is_compact":bool(compact)}
|
|
101
|
+
@staticmethod
|
|
102
|
+
def rbutton(text, **action):
|
|
103
|
+
if len(action)!=1: raise ValueError("زر Rich يحتاج إجراء واحد")
|
|
104
|
+
return {"text":str(text), **action}
|
|
105
|
+
@staticmethod
|
|
106
|
+
def rbuttons(*rows, align="center"):
|
|
107
|
+
return {"type":"buttons","buttons":[dict(x) for row in rows for x in row],"align":align if align in ("left","center","right") else "center"}
|
|
108
|
+
@staticmethod
|
|
109
|
+
def rich(*blocks, is_rtl=True): return {"is_rtl":bool(is_rtl),"blocks":list(blocks)}
|
|
110
|
+
@staticmethod
|
|
111
|
+
def rich_json(*blocks, is_rtl=True, indent=2):
|
|
112
|
+
return json.dumps({"rich_message":K.rich(*blocks,is_rtl=is_rtl)},ensure_ascii=False,indent=indent)
|
|
113
|
+
@staticmethod
|
|
114
|
+
def help():
|
|
115
|
+
return """KFormat guide
|
|
116
|
+
b(text) عريض
|
|
117
|
+
i(text) مائل
|
|
118
|
+
un(text) تسطير
|
|
119
|
+
st(text) مشطوب
|
|
120
|
+
sp(text) نص مخفي
|
|
121
|
+
c(text) كود داخل السطر
|
|
122
|
+
pre(text,lang) كتلة كود
|
|
123
|
+
a(text,url) رابط
|
|
124
|
+
tg(text,id) منشن بالآيدي
|
|
125
|
+
block(text) اقتباس
|
|
126
|
+
expand(text) اقتباس قابل للطي
|
|
127
|
+
table(rows, headers=[]) جدول نصي
|
|
128
|
+
buttons(*rows) أزرار Inline
|
|
129
|
+
btn(text,data=...) أو btn(text,url=...)
|
|
130
|
+
ul(*items), ol(*items), bullet(text), hr()
|
|
131
|
+
rb/rh/rdiv/rtable/rbutton/rbuttons/rich/rich_json لبناء Rich Message JSON
|
|
132
|
+
|
|
133
|
+
دوال النص تحتاج parse_mode='HTML'.
|
|
134
|
+
الجدول العادي نص monospace مرتب، مو خلايا أصلية في sendMessage.
|
|
135
|
+
دوال Rich تبني JSON فقط، ولا ترسله وحدها.
|
|
136
|
+
"""
|
|
137
|
+
bold=K.b
|
|
138
|
+
italic=K.i
|
|
139
|
+
underline=K.un
|
|
140
|
+
strike=K.st
|
|
141
|
+
spoiler=K.sp
|
|
142
|
+
code=K.c
|
|
143
|
+
table=K.table
|
|
144
|
+
buttons=K.buttons
|
|
File without changes
|