python-zatolox-bot 0.1.0__tar.gz → 0.1.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-zatolox-bot
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: SDK ufficiale per costruire bot su Zatolox
5
5
  Home-page: https://github.com/zatolox/python-zatolox-bot
6
6
  Author: Zatolox
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-zatolox-bot
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: SDK ufficiale per costruire bot su Zatolox
5
5
  Home-page: https://github.com/zatolox/python-zatolox-bot
6
6
  Author: Zatolox
@@ -5,7 +5,7 @@ from setuptools import setup, find_packages
5
5
  # sono, meno se ne rompono.
6
6
  setup(
7
7
  name="python-zatolox-bot",
8
- version="0.1.0",
8
+ version="0.1.2",
9
9
  description="SDK ufficiale per costruire bot su Zatolox",
10
10
  long_description=(
11
11
  "Wrapper leggero attorno al gateway HTTP dei bot di Zatolox. "
@@ -3,5 +3,5 @@
3
3
  from .bot import ZatoloxBot, ZatoloxApiError
4
4
  from .types import User, Message, CallbackQuery, Update
5
5
 
6
- __version__ = "0.1.0"
6
+ __version__ = "0.1.2"
7
7
  __all__ = ["ZatoloxBot", "ZatoloxApiError", "User", "Message", "CallbackQuery", "Update"]
@@ -45,6 +45,9 @@ class ZatoloxBot:
45
45
  self.command_handlers: Dict[str, Callable[[Message], None]] = {}
46
46
  self._message_handler: Optional[Callable[[Message], None]] = None
47
47
  self._callback_handler: Optional[Callable[[CallbackQuery], None]] = None
48
+ # Handler di callback CON PREDICATO (@bot.callback_query_handler(func=...)): coppie
49
+ # (predicato, funzione). Il primo il cui predicato e' vero riceve il click.
50
+ self._callback_query_handlers: List[tuple] = []
48
51
 
49
52
  # Offset del prossimo getUpdates. Avanza a `ultimo update_id + 1`: e' cosi' che si conferma
50
53
  # al server di aver ricevuto tutto cio' che precede, e non lo si riceve piu'.
@@ -91,6 +94,61 @@ class ZatoloxBot:
91
94
  result = self._call("getUpdates", params, http="get") or []
92
95
  return [Update.from_dict(u) for u in result]
93
96
 
97
+ def set_my_commands(self, commands: List[Any]) -> List[dict]:
98
+ """
99
+ Registra i comandi del bot sul server (come setMyCommands di Telegram).
100
+
101
+ Il client Zatolox li mostra nel menu "/" della barra input: senza almeno un comando quel
102
+ menu non compare proprio. Da chiamare una volta all'avvio.
103
+
104
+ `commands` accetta forme diverse, per comodita':
105
+ - dict: {"command": "start", "description": "avvia il bot"}
106
+ - tupla: ("start", "avvia il bot")
107
+ Lo slash iniziale e' facoltativo: lo normalizza il server. Un array vuoto azzera i comandi.
108
+ """
109
+ norm: List[dict] = []
110
+ for c in commands:
111
+ if isinstance(c, dict):
112
+ norm.append({"command": str(c.get("command", "")), "description": str(c.get("description", ""))})
113
+ elif isinstance(c, (tuple, list)) and len(c) >= 2:
114
+ norm.append({"command": str(c[0]), "description": str(c[1])})
115
+ return self._call("setMyCommands", {"commands": norm})
116
+
117
+ def edit_message_text(
118
+ self, chat_id: str, message_id: str, text: str, reply_markup: Optional[Any] = None
119
+ ) -> Optional[Message]:
120
+ """
121
+ Riscrive un PROPRIO messaggio (testo + eventuali pulsanti). La via per navigare un menu:
122
+ clic -> callback -> edit del testo e dei pulsanti, senza inviare un nuovo messaggio.
123
+
124
+ Se passi `reply_markup` aggiorna la tastiera (None esplicito la rimuove); se lo ometti, i
125
+ pulsanti restano quelli di prima. Un bot puo' modificare solo i messaggi che ha inviato lui.
126
+ """
127
+ payload: Dict[str, Any] = {"chat_id": chat_id, "message_id": message_id, "text": text}
128
+ if reply_markup is not None:
129
+ payload["reply_markup"] = reply_markup
130
+ return Message.from_dict(self._call("editMessageText", payload))
131
+
132
+ def edit_message_reply_markup(
133
+ self, chat_id: str, message_id: str, reply_markup: Optional[Any] = None
134
+ ) -> Optional[Message]:
135
+ """Cambia SOLO i pulsanti di un proprio messaggio, senza toccarne il testo (utile per i
136
+ sottomenu). `reply_markup=None` toglie la tastiera."""
137
+ return Message.from_dict(self._call(
138
+ "editMessageReplyMarkup",
139
+ {"chat_id": chat_id, "message_id": message_id, "reply_markup": reply_markup}
140
+ ))
141
+
142
+ def answer_callback_query(self, callback_query_id: str, text: Optional[str] = None) -> Any:
143
+ """
144
+ Conferma un click su un pulsante inline. Con `text` mostra un piccolo avviso a chi ha
145
+ toccato; senza, e' solo un ack. Da chiamare dentro l'handler della callback.
146
+ """
147
+ payload: Dict[str, Any] = {"callback_query_id": callback_query_id}
148
+ if text is not None:
149
+ payload["text"] = text
150
+ return self._call("answerCallbackQuery", payload)
151
+
94
152
  # ------------------------------------------------------------------ handler
95
153
 
96
154
  def add_command_handler(self, command: str, callback: Callable[[Message], None]) -> None:
@@ -113,6 +171,43 @@ class ZatoloxBot:
113
171
  return fn
114
172
  return deco
115
173
 
174
+ def message_handler(self, fn: Optional[Callable[[Message], None]] = None):
175
+ """
176
+ Decoratore per il fallback sul testo LIBERO (i messaggi che non sono comandi) — la base per
177
+ un bot conversazionale (es. inoltrare il testo a un'IA). Equivale ad add_message_handler.
178
+
179
+ Funziona sia nudo sia con le parentesi::
180
+
181
+ @bot.message_handler
182
+ def on_text(msg): ...
183
+
184
+ @bot.message_handler()
185
+ def on_text(msg): ...
186
+ """
187
+ def register(f: Callable[[Message], None]) -> Callable[[Message], None]:
188
+ self.add_message_handler(f)
189
+ return f
190
+ # Uso nudo (@bot.message_handler): Python passa gia' la funzione qui.
191
+ return register(fn) if fn is not None else register
192
+
193
+ def callback_query_handler(self, func: Optional[Callable[[CallbackQuery], bool]] = None):
194
+ """
195
+ Decoratore per i click sui pulsanti inline, con un filtro:
196
+
197
+ @bot.callback_query_handler(func=lambda call: call.data == "next")
198
+ def on_next(call): ...
199
+
200
+ `func` e' un predicato sul CallbackQuery: il PRIMO handler il cui predicato e' vero riceve il
201
+ click. Ometti `func` (o passa None) per prenderli tutti. Coesiste con add_callback_handler,
202
+ che resta come fallback finale.
203
+ """
204
+ predicate = func if callable(func) else (lambda call: True)
205
+
206
+ def deco(fn: Callable[[CallbackQuery], None]) -> Callable[[CallbackQuery], None]:
207
+ self._callback_query_handlers.append((predicate, fn))
208
+ return fn
209
+ return deco
210
+
116
211
  # ------------------------------------------------------------------ instradamento
117
212
 
118
213
  def _dispatch(self, update: Update) -> None:
@@ -120,8 +215,16 @@ class ZatoloxBot:
120
215
  stampata, non propagata, cosi' un handler difettoso non ferma il polling."""
121
216
  try:
122
217
  if update.callback_query is not None:
218
+ cb = update.callback_query
219
+ # Prima gli handler con predicato, in ordine di registrazione: vince il primo che
220
+ # accetta. E' cio' che permette di smistare per callback_data (menu/sottomenu).
221
+ for predicate, fn in self._callback_query_handlers:
222
+ if predicate(cb):
223
+ fn(cb)
224
+ return
225
+ # Nessuno ha accettato: fallback al singolo handler generico, se c'e'.
123
226
  if self._callback_handler:
124
- self._callback_handler(update.callback_query)
227
+ self._callback_handler(cb)
125
228
  return
126
229
 
127
230
  msg = update.message