Rubka 3.2.5__py3-none-any.whl → 4.3.4__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.
rubka/api.py CHANGED
@@ -73,6 +73,8 @@ class Robot:
73
73
  self._callback_handler = None
74
74
  self._message_handler = None
75
75
  self._inline_query_handler = None
76
+ self._callback_handlers = None
77
+ self._callback_handlers = [] # ✅ این خط مهمه
76
78
 
77
79
 
78
80
  logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
@@ -108,7 +110,16 @@ class Robot:
108
110
  return func
109
111
  return decorator
110
112
 
111
-
113
+ def on_callback(self, button_id: Optional[str] = None):
114
+ def decorator(func: Callable[[Any, Message], None]):
115
+ if not hasattr(self, "_callback_handlers"):
116
+ self._callback_handlers = []
117
+ self._callback_handlers.append({
118
+ "func": func,
119
+ "button_id": button_id
120
+ })
121
+ return func
122
+ return decorator
112
123
  def on_inline_query(self):
113
124
  def decorator(func: Callable[[Any, InlineMessage], None]):
114
125
  self._inline_query_handler = func
@@ -116,12 +127,14 @@ class Robot:
116
127
  return decorator
117
128
 
118
129
  def _process_update(self, update: Dict[str, Any]):
119
- import threading
130
+ import threading, time
131
+
120
132
  if update.get('type') == 'ReceiveQuery':
121
133
  msg = update.get("inline_message", {})
122
134
  if self._inline_query_handler:
123
135
  context = InlineMessage(bot=self, raw_data=msg)
124
136
  threading.Thread(target=self._inline_query_handler, args=(self, context), daemon=True).start()
137
+ return # اگر inline بود ادامه نده
125
138
 
126
139
  if update.get('type') == 'NewMessage':
127
140
  msg = update.get('new_message', {})
@@ -129,16 +142,27 @@ class Robot:
129
142
  message_id = msg.get('message_id')
130
143
  sender_id = msg.get('sender_id')
131
144
  text = msg.get('text')
145
+
146
+ # فیلتر زمان قدیمی
132
147
  try:
133
- import time
134
148
  if msg.get("time") and (time.time() - float(msg["time"])) > 20:
135
149
  return
136
150
  except Exception:
137
151
  return
138
152
 
153
+ # ساخت context یکجا
154
+ context = Message(bot=self, chat_id=chat_id, message_id=message_id, sender_id=sender_id, text=text, raw_data=msg)
155
+
156
+ # 💠 اول بررسی دکمه‌ها (callback)
157
+ if context.aux_data and hasattr(self, "_callback_handlers"):
158
+ for handler in self._callback_handlers:
159
+ if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
160
+ threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
161
+ return # فقط اولین callback اجرا شود
162
+
163
+ # 💠 بعد بررسی پیام‌های متنی معمولی
139
164
  if self._message_handler:
140
165
  handler = self._message_handler
141
- context = Message(bot=self, chat_id=chat_id, message_id=message_id, sender_id=sender_id, text=text, raw_data=msg)
142
166
 
143
167
  if handler["commands"]:
144
168
  if not context.text or not context.text.startswith("/"):
@@ -149,22 +173,11 @@ class Robot:
149
173
  return
150
174
  context.args = parts[1:]
151
175
 
152
- if handler["filters"]:
153
- if not handler["filters"](context):
154
- return
176
+ if handler["filters"] and not handler["filters"](context):
177
+ return
155
178
 
156
179
  threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
157
180
 
158
- elif update.get('type') == 'ReceiveQuery':
159
- msg = update.get("inline_message", {})
160
- chat_id = msg.get("chat_id")
161
- message_id = msg.get("message_id")
162
- sender_id = msg.get("sender_id")
163
- text = msg.get("text")
164
-
165
- if hasattr(self, "_callback_handler"):
166
- context = Message(bot=self, chat_id=chat_id, message_id=message_id, sender_id=sender_id, text=text, raw_data=msg)
167
- threading.Thread(target=self._callback_handler, args=(self, context), daemon=True).start()
168
181
 
169
182
 
170
183
 
@@ -222,9 +235,40 @@ class Robot:
222
235
 
223
236
  return self._post("sendMessage", payload)
224
237
 
225
- def Get_Member_channel(self,object_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
226
- getter = Client_get(show_last_six_words(self.token))
227
- return getter.get_all_members(object_guid,search_text,start_id,just_get_guids)
238
+ def _get_client(self):
239
+ return Client_get(show_last_six_words(self.token))
240
+
241
+ def check_join(self, channel_guid: str, chat_id: str = None) -> bool | list[str]:
242
+ client = self._get_client()
243
+
244
+ if chat_id:
245
+ chat_info = self.get_chat(chat_id).get('data', {}).get('chat', {})
246
+ username = chat_info.get('username')
247
+ user_id = chat_info.get('user_id')
248
+
249
+ if username:
250
+ members = self.get_all_member(channel_guid, search_text=username).get('in_chat_members', [])
251
+ return any(m.get('username') == username for m in members)
252
+
253
+ elif user_id:
254
+ member_guids = client.get_all_members(channel_guid, just_get_guids=True)
255
+ return user_id in member_guids
256
+
257
+ return False
258
+
259
+ return False
260
+
261
+
262
+ def get_all_member(
263
+ self,
264
+ channel_guid: str,
265
+ search_text: str = None,
266
+ start_id: str = None,
267
+ just_get_guids: bool = False
268
+ ):
269
+ client = self._get_client()
270
+ return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
271
+
228
272
  def send_poll(
229
273
  self,
230
274
  chat_id: str,
@@ -373,3 +417,23 @@ class Robot:
373
417
  "chat_keypad_type": "New",
374
418
  "chat_keypad": chat_keypad
375
419
  })
420
+ def get_name(self, chat_id: str) -> str:
421
+ try:
422
+ chat = self.get_chat(chat_id)
423
+ chat_info = chat.get("data", {}).get("chat", {})
424
+ first_name = chat_info.get("first_name", "")
425
+ last_name = chat_info.get("last_name", "")
426
+
427
+ if first_name and last_name:
428
+ return f"{first_name} {last_name}"
429
+ elif first_name:
430
+ return first_name
431
+ elif last_name:
432
+ return last_name
433
+ else:
434
+ return "Unknown"
435
+ except Exception:
436
+ return "Unknown"
437
+ def get_username(self, chat_id: str) -> str:
438
+ chat_info = self.get_chat(chat_id).get("data", {}).get("chat", {})
439
+ return chat_info.get("username", "None")
rubka/button.py ADDED
@@ -0,0 +1,264 @@
1
+ from typing import Dict
2
+
3
+ from typing import Dict, List, Optional, Union
4
+ from typing import Dict, List, Optional
5
+
6
+ class InlineBuilder:
7
+ def __init__(self):
8
+ self.rows: List[Dict] = []
9
+
10
+ def row(self, *buttons: Dict) -> "InlineBuilder":
11
+ """
12
+ افزودن یک ردیف دکمه به کیبورد
13
+ حداقل یک دکمه باید داده شود.
14
+ """
15
+ if not buttons:
16
+ raise ValueError("حداقل یک دکمه باید به row داده شود")
17
+ self.rows.append({"buttons": list(buttons)})
18
+ return self
19
+
20
+ def button_simple(self, id: str, text: str) -> Dict:
21
+ return {"id": id, "type": "Simple", "button_text": text}
22
+
23
+ def button_selection(self, id: str, text: str, selection: Dict) -> Dict:
24
+ """
25
+ selection: dict با فیلدهای:
26
+ - selection_id (str)
27
+ - search_type (str) [ButtonSelectionSearchEnum: None, Local, Api]
28
+ - get_type (str) [ButtonSelectionGetEnum: Local, Api]
29
+ - items (list of ButtonSelectionItem)
30
+ - is_multi_selection (bool)
31
+ - columns_count (str)
32
+ - title (str)
33
+ """
34
+ return {
35
+ "id": id,
36
+ "type": "Selection",
37
+ "button_text": text,
38
+ "button_selection": selection
39
+ }
40
+
41
+ def button_calendar(self, id: str, title: str, type_: str,
42
+ default_value: Optional[str] = None,
43
+ min_year: Optional[str] = None,
44
+ max_year: Optional[str] = None) -> Dict:
45
+ """
46
+ type_: ButtonCalendarTypeEnum = "DatePersian" | "DateGregorian"
47
+ """
48
+ calendar = {
49
+ "title": title,
50
+ "type": type_,
51
+ }
52
+ if default_value:
53
+ calendar["default_value"] = default_value
54
+ if min_year:
55
+ calendar["min_year"] = min_year
56
+ if max_year:
57
+ calendar["max_year"] = max_year
58
+
59
+ return {
60
+ "id": id,
61
+ "type": "Calendar",
62
+ "button_text": title,
63
+ "button_calendar": calendar
64
+ }
65
+
66
+ def button_number_picker(self, id: str, title: str, min_value: str, max_value: str,
67
+ default_value: Optional[str] = None) -> Dict:
68
+ picker = {
69
+ "title": title,
70
+ "min_value": min_value,
71
+ "max_value": max_value,
72
+ }
73
+ if default_value:
74
+ picker["default_value"] = default_value
75
+
76
+ return {
77
+ "id": id,
78
+ "type": "NumberPicker",
79
+ "button_text": title,
80
+ "button_number_picker": picker
81
+ }
82
+
83
+ def button_string_picker(self, id: str, title: Optional[str], items: List[str],
84
+ default_value: Optional[str] = None) -> Dict:
85
+ picker = {
86
+ "items": items
87
+ }
88
+ if default_value:
89
+ picker["default_value"] = default_value
90
+ if title:
91
+ picker["title"] = title
92
+
93
+ return {
94
+ "id": id,
95
+ "type": "StringPicker",
96
+ "button_text": title if title else "انتخاب",
97
+ "button_string_picker": picker
98
+ }
99
+
100
+ def button_location(self, id: str, type_: str, location_image_url: str,
101
+ default_pointer_location: Optional[Dict] = None,
102
+ default_map_location: Optional[Dict] = None,
103
+ title: Optional[str] = None) -> Dict:
104
+ """
105
+ type_: ButtonLocationTypeEnum = "Picker" | "View"
106
+ location_image_url: str آدرس عکس دکمه موقعیت
107
+ default_pointer_location و default_map_location هر کدام دیکشنری Location (latitude, longitude)
108
+ """
109
+ loc = {
110
+ "type": type_,
111
+ "location_image_url": location_image_url,
112
+ }
113
+ if default_pointer_location:
114
+ loc["default_pointer_location"] = default_pointer_location
115
+ if default_map_location:
116
+ loc["default_map_location"] = default_map_location
117
+ if title:
118
+ loc["title"] = title
119
+
120
+ return {
121
+ "id": id,
122
+ "type": "Location",
123
+ "button_text": title if title else "موقعیت مکانی",
124
+ "button_location": loc
125
+ }
126
+
127
+ def button_textbox(self, id: str, title: Optional[str],
128
+ type_line: str, type_keypad: str,
129
+ place_holder: Optional[str] = None,
130
+ default_value: Optional[str] = None) -> Dict:
131
+ """
132
+ type_line: ButtonTextboxTypeLineEnum = "SingleLine" | "MultiLine"
133
+ type_keypad: ButtonTextboxTypeKeypadEnum = "String" | "Number"
134
+ """
135
+ textbox = {
136
+ "type_line": type_line,
137
+ "type_keypad": type_keypad
138
+ }
139
+ if place_holder:
140
+ textbox["place_holder"] = place_holder
141
+ if default_value:
142
+ textbox["default_value"] = default_value
143
+ if title:
144
+ textbox["title"] = title
145
+
146
+ return {
147
+ "id": id,
148
+ "type": "Textbox",
149
+ "button_text": title if title else "متن",
150
+ "button_textbox": textbox
151
+ }
152
+
153
+ def button_payment(self, id: str, title: str, amount: int, description: Optional[str] = None) -> Dict:
154
+ """
155
+ نمونه‌ای ساده برای دکمه پرداخت (مقدار و توضیح دلخواه)
156
+ """
157
+ payment = {
158
+ "title": title,
159
+ "amount": amount
160
+ }
161
+ if description:
162
+ payment["description"] = description
163
+
164
+ return {
165
+ "id": id,
166
+ "type": "Payment",
167
+ "button_text": title,
168
+ "button_payment": payment
169
+ }
170
+
171
+ def button_camera_image(self, id: str, title: str) -> Dict:
172
+ return {
173
+ "id": id,
174
+ "type": "CameraImage",
175
+ "button_text": title
176
+ }
177
+
178
+ def button_camera_video(self, id: str, title: str) -> Dict:
179
+ return {
180
+ "id": id,
181
+ "type": "CameraVideo",
182
+ "button_text": title
183
+ }
184
+
185
+ def button_gallery_image(self, id: str, title: str) -> Dict:
186
+ return {
187
+ "id": id,
188
+ "type": "GalleryImage",
189
+ "button_text": title
190
+ }
191
+
192
+ def button_gallery_video(self, id: str, title: str) -> Dict:
193
+ return {
194
+ "id": id,
195
+ "type": "GalleryVideo",
196
+ "button_text": title
197
+ }
198
+
199
+ def button_file(self, id: str, title: str) -> Dict:
200
+ return {
201
+ "id": id,
202
+ "type": "File",
203
+ "button_text": title
204
+ }
205
+
206
+ def button_audio(self, id: str, title: str) -> Dict:
207
+ return {
208
+ "id": id,
209
+ "type": "Audio",
210
+ "button_text": title
211
+ }
212
+
213
+ def button_record_audio(self, id: str, title: str) -> Dict:
214
+ return {
215
+ "id": id,
216
+ "type": "RecordAudio",
217
+ "button_text": title
218
+ }
219
+
220
+ def button_my_phone_number(self, id: str, title: str) -> Dict:
221
+ return {
222
+ "id": id,
223
+ "type": "MyPhoneNumber",
224
+ "button_text": title
225
+ }
226
+
227
+ def button_my_location(self, id: str, title: str) -> Dict:
228
+ return {
229
+ "id": id,
230
+ "type": "MyLocation",
231
+ "button_text": title
232
+ }
233
+
234
+ def button_link(self, id: str, title: str, url: str) -> Dict:
235
+ return {
236
+ "id": id,
237
+ "type": "Link",
238
+ "button_text": title,
239
+ "url": url
240
+ }
241
+
242
+ def button_ask_my_phone_number(self, id: str, title: str) -> Dict:
243
+ return {
244
+ "id": id,
245
+ "type": "AskMyPhoneNumber",
246
+ "button_text": title
247
+ }
248
+
249
+ def button_ask_location(self, id: str, title: str) -> Dict:
250
+ return {
251
+ "id": id,
252
+ "type": "AskLocation",
253
+ "button_text": title
254
+ }
255
+
256
+ def button_barcode(self, id: str, title: str) -> Dict:
257
+ return {
258
+ "id": id,
259
+ "type": "Barcode",
260
+ "button_text": title
261
+ }
262
+
263
+ def build(self) -> Dict:
264
+ return {"rows": self.rows}
rubka/context.py CHANGED
@@ -195,7 +195,6 @@ class Message:
195
195
  self.time: str = self.raw_data.get("time")
196
196
  self.is_edited: bool = self.raw_data.get("is_edited", False)
197
197
  self.sender_type: str = self.raw_data.get("sender_type")
198
-
199
198
  self.args = []
200
199
  self.reply_to_message_id: Optional[str] = self.raw_data.get("reply_to_message_id")
201
200
  self.forwarded_from = ForwardedFrom(self.raw_data["forwarded_from"]) if "forwarded_from" in self.raw_data else None
rubka/keypad.py CHANGED
@@ -1,18 +1,268 @@
1
1
  from typing import Dict
2
2
 
3
+ from typing import Dict, List, Optional, Union
4
+ from typing import Dict, List, Optional
5
+
3
6
  class InlineBuilder:
4
7
  def __init__(self):
5
- self.rows = []
8
+ self.rows: List[Dict] = []
6
9
 
7
- def row(self, *buttons: Dict[str, str]):
10
+ def row(self, *buttons: Dict) -> "InlineBuilder":
11
+ """
12
+ افزودن یک ردیف دکمه به کیبورد
13
+ حداقل یک دکمه باید داده شود.
14
+ """
15
+ if not buttons:
16
+ raise ValueError("حداقل یک دکمه باید به row داده شود")
8
17
  self.rows.append({"buttons": list(buttons)})
9
18
  return self
10
19
 
11
- def button(self, id: str, text: str, type: str = "Simple") -> Dict[str, str]:
12
- return {"id": id, "type": type, "button_text": text}
20
+ def button_simple(self, id: str, text: str) -> Dict:
21
+ return {"id": id, "type": "Simple", "button_text": text}
22
+
23
+ def button_selection(self, id: str, text: str, selection: Dict) -> Dict:
24
+ """
25
+ selection: dict با فیلدهای:
26
+ - selection_id (str)
27
+ - search_type (str) [ButtonSelectionSearchEnum: None, Local, Api]
28
+ - get_type (str) [ButtonSelectionGetEnum: Local, Api]
29
+ - items (list of ButtonSelectionItem)
30
+ - is_multi_selection (bool)
31
+ - columns_count (str)
32
+ - title (str)
33
+ """
34
+ return {
35
+ "id": id,
36
+ "type": "Selection",
37
+ "button_text": text,
38
+ "button_selection": selection
39
+ }
40
+
41
+ def button_calendar(self, id: str, title: str, type_: str,
42
+ default_value: Optional[str] = None,
43
+ min_year: Optional[str] = None,
44
+ max_year: Optional[str] = None) -> Dict:
45
+ """
46
+ type_: ButtonCalendarTypeEnum = "DatePersian" | "DateGregorian"
47
+ """
48
+ calendar = {
49
+ "title": title,
50
+ "type": type_,
51
+ }
52
+ if default_value:
53
+ calendar["default_value"] = default_value
54
+ if min_year:
55
+ calendar["min_year"] = min_year
56
+ if max_year:
57
+ calendar["max_year"] = max_year
58
+
59
+ return {
60
+ "id": id,
61
+ "type": "Calendar",
62
+ "button_text": title,
63
+ "button_calendar": calendar
64
+ }
65
+
66
+ def button_number_picker(self, id: str, title: str, min_value: str, max_value: str,
67
+ default_value: Optional[str] = None) -> Dict:
68
+ picker = {
69
+ "title": title,
70
+ "min_value": min_value,
71
+ "max_value": max_value,
72
+ }
73
+ if default_value:
74
+ picker["default_value"] = default_value
75
+
76
+ return {
77
+ "id": id,
78
+ "type": "NumberPicker",
79
+ "button_text": title,
80
+ "button_number_picker": picker
81
+ }
82
+
83
+ def button_string_picker(self, id: str, title: Optional[str], items: List[str],
84
+ default_value: Optional[str] = None) -> Dict:
85
+ picker = {
86
+ "items": items
87
+ }
88
+ if default_value:
89
+ picker["default_value"] = default_value
90
+ if title:
91
+ picker["title"] = title
92
+
93
+ return {
94
+ "id": id,
95
+ "type": "StringPicker",
96
+ "button_text": title if title else "انتخاب",
97
+ "button_string_picker": picker
98
+ }
99
+
100
+ def button_location(self, id: str, type_: str, location_image_url: str,
101
+ default_pointer_location: Optional[Dict] = None,
102
+ default_map_location: Optional[Dict] = None,
103
+ title: Optional[str] = None) -> Dict:
104
+ """
105
+ type_: ButtonLocationTypeEnum = "Picker" | "View"
106
+ location_image_url: str آدرس عکس دکمه موقعیت
107
+ default_pointer_location و default_map_location هر کدام دیکشنری Location (latitude, longitude)
108
+ """
109
+ loc = {
110
+ "type": type_,
111
+ "location_image_url": location_image_url,
112
+ }
113
+ if default_pointer_location:
114
+ loc["default_pointer_location"] = default_pointer_location
115
+ if default_map_location:
116
+ loc["default_map_location"] = default_map_location
117
+ if title:
118
+ loc["title"] = title
119
+
120
+ return {
121
+ "id": id,
122
+ "type": "Location",
123
+ "button_text": title if title else "موقعیت مکانی",
124
+ "button_location": loc
125
+ }
126
+
127
+ def button_textbox(self, id: str, title: Optional[str],
128
+ type_line: str, type_keypad: str,
129
+ place_holder: Optional[str] = None,
130
+ default_value: Optional[str] = None) -> Dict:
131
+ """
132
+ type_line: ButtonTextboxTypeLineEnum = "SingleLine" | "MultiLine"
133
+ type_keypad: ButtonTextboxTypeKeypadEnum = "String" | "Number"
134
+ """
135
+ textbox = {
136
+ "type_line": type_line,
137
+ "type_keypad": type_keypad
138
+ }
139
+ if place_holder:
140
+ textbox["place_holder"] = place_holder
141
+ if default_value:
142
+ textbox["default_value"] = default_value
143
+ if title:
144
+ textbox["title"] = title
145
+
146
+ return {
147
+ "id": id,
148
+ "type": "Textbox",
149
+ "button_text": title if title else "متن",
150
+ "button_textbox": textbox
151
+ }
152
+
153
+ def button_payment(self, id: str, title: str, amount: int, description: Optional[str] = None) -> Dict:
154
+ """
155
+ نمونه‌ای ساده برای دکمه پرداخت (مقدار و توضیح دلخواه)
156
+ """
157
+ payment = {
158
+ "title": title,
159
+ "amount": amount
160
+ }
161
+ if description:
162
+ payment["description"] = description
163
+
164
+ return {
165
+ "id": id,
166
+ "type": "Payment",
167
+ "button_text": title,
168
+ "button_payment": payment
169
+ }
170
+
171
+ def button_camera_image(self, id: str, title: str) -> Dict:
172
+ return {
173
+ "id": id,
174
+ "type": "CameraImage",
175
+ "button_text": title
176
+ }
177
+
178
+ def button_camera_video(self, id: str, title: str) -> Dict:
179
+ return {
180
+ "id": id,
181
+ "type": "CameraVideo",
182
+ "button_text": title
183
+ }
184
+
185
+ def button_gallery_image(self, id: str, title: str) -> Dict:
186
+ return {
187
+ "id": id,
188
+ "type": "GalleryImage",
189
+ "button_text": title
190
+ }
191
+
192
+ def button_gallery_video(self, id: str, title: str) -> Dict:
193
+ return {
194
+ "id": id,
195
+ "type": "GalleryVideo",
196
+ "button_text": title
197
+ }
198
+
199
+ def button_file(self, id: str, title: str) -> Dict:
200
+ return {
201
+ "id": id,
202
+ "type": "File",
203
+ "button_text": title
204
+ }
205
+
206
+ def button_audio(self, id: str, title: str) -> Dict:
207
+ return {
208
+ "id": id,
209
+ "type": "Audio",
210
+ "button_text": title
211
+ }
13
212
 
14
- def build(self):
213
+ def button_record_audio(self, id: str, title: str) -> Dict:
214
+ return {
215
+ "id": id,
216
+ "type": "RecordAudio",
217
+ "button_text": title
218
+ }
219
+
220
+ def button_my_phone_number(self, id: str, title: str) -> Dict:
221
+ return {
222
+ "id": id,
223
+ "type": "MyPhoneNumber",
224
+ "button_text": title
225
+ }
226
+
227
+ def button_my_location(self, id: str, title: str) -> Dict:
228
+ return {
229
+ "id": id,
230
+ "type": "MyLocation",
231
+ "button_text": title
232
+ }
233
+
234
+ def button_link(self, id: str, title: str, url: str) -> Dict:
235
+ return {
236
+ "id": id,
237
+ "type": "Link",
238
+ "button_text": title,
239
+ "url": url
240
+ }
241
+
242
+ def button_ask_my_phone_number(self, id: str, title: str) -> Dict:
243
+ return {
244
+ "id": id,
245
+ "type": "AskMyPhoneNumber",
246
+ "button_text": title
247
+ }
248
+
249
+ def button_ask_location(self, id: str, title: str) -> Dict:
250
+ return {
251
+ "id": id,
252
+ "type": "AskLocation",
253
+ "button_text": title
254
+ }
255
+
256
+ def button_barcode(self, id: str, title: str) -> Dict:
257
+ return {
258
+ "id": id,
259
+ "type": "Barcode",
260
+ "button_text": title
261
+ }
262
+
263
+ def build(self) -> Dict:
15
264
  return {"rows": self.rows}
265
+
16
266
  from typing import List, Dict, Optional
17
267
 
18
268
  class ChatKeypadBuilder:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rubka
3
- Version: 3.2.5
3
+ Version: 4.3.4
4
4
  Summary: A Python library for interacting with Rubika Bot API.
5
5
  Home-page: https://github.com/Mahdy-Ahmadi/Rubka
6
6
  Download-URL: https://github.com/Mahdy-Ahmadi/Rubka/archive/refs/tags/v0.1.0.tar.gz
@@ -1,12 +1,13 @@
1
1
  rubka/__init__.py,sha256=3f4H6Uj1ylrfBYlTHmTvzZSvaPJsdNhCocyX3Bbvuv0,254
2
- rubka/api.py,sha256=rAVyt11qzPb3gvz0ouuBlTASC_LrEaUGb9Em-G3XLfE,13805
2
+ rubka/api.py,sha256=2Oe7fZ6HkCBVt654RgpAgMQjUz8zRq5225cHJtBJGck,16104
3
+ rubka/button.py,sha256=4fMSZR7vUADxSmw1R3_pZ4dw5uMLZX5sOkwPPyNTBDE,8437
3
4
  rubka/config.py,sha256=Bck59xkOiqioLv0GkQ1qPGnBXVctz1hKk6LT4h2EPx0,78
4
- rubka/context.py,sha256=5OMFjcnMWvkn3ZigRgJrjMiFt_tYgMwzHsTS6qXMGj8,12843
5
+ rubka/context.py,sha256=BSb_O1PiB0QEPeY_u2F97oIVxg-3J9Re2iV1HWywz4M,12841
5
6
  rubka/decorators.py,sha256=hGwUoE4q2ImrunJIGJ_kzGYYxQf1ueE0isadqraKEts,1157
6
7
  rubka/exceptions.py,sha256=tujZt1XrhWaw-lmdeVadVceUptpw4XzNgE44sAAY0gs,90
7
8
  rubka/jobs.py,sha256=GvLMLsVhcSEzRTgkvnPISPEBN71suW2xXI0hUaUZPTo,378
8
9
  rubka/keyboards.py,sha256=7nr-dT2bQJVQnQ6RMWPTSjML6EEk6dsBx-4d8pab8xk,488
9
- rubka/keypad.py,sha256=FHe0xVYhOXMdHZhbGKHsRxtsRB27qZv0DvNfb8NkNwI,1545
10
+ rubka/keypad.py,sha256=yGsNt8W5HtUFBzVF1m_p7GySlu1hwIcSvXZ4BTdrlvg,9558
10
11
  rubka/logger.py,sha256=J2I6NiK1z32lrAzC4H1Et6WPMBXxXGCVUsW4jgcAofs,289
11
12
  rubka/utils.py,sha256=XUQUZxQt9J2f0X5hmAH_MH1kibTAfdT1T4AaBkBhBBs,148
12
13
  rubka/adaptorrubka/__init__.py,sha256=6o2tCXnVeES7nx-LjnzsuMqjKcWIm9qwKficLE54s-U,83
@@ -30,7 +31,7 @@ rubka/adaptorrubka/types/socket/message.py,sha256=0WgLMZh4eow8Zn7AiSX4C3GZjQTkIg
30
31
  rubka/adaptorrubka/utils/__init__.py,sha256=OgCFkXdNFh379quNwIVOAWY2NP5cIOxU5gDRRALTk4o,54
31
32
  rubka/adaptorrubka/utils/configs.py,sha256=nMUEOJh1NqDJsf9W9PurkN_DLYjO6kKPMm923i4Jj_A,492
32
33
  rubka/adaptorrubka/utils/utils.py,sha256=5-LioLNYX_TIbQGDeT50j7Sg9nAWH2LJUUs-iEXpsUY,8816
33
- rubka-3.2.5.dist-info/METADATA,sha256=z_2CQsabaLqFe4s7pQ-tj8AxeEI8j48LUvzcWPqoWfE,8168
34
- rubka-3.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
- rubka-3.2.5.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
36
- rubka-3.2.5.dist-info/RECORD,,
34
+ rubka-4.3.4.dist-info/METADATA,sha256=qtXGjYKe_MxSogYEP_j32a8xHPXViqVkmSdZLB5atNA,8168
35
+ rubka-4.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
+ rubka-4.3.4.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
37
+ rubka-4.3.4.dist-info/RECORD,,
File without changes