Rubka 0.1.2__tar.gz → 1.2.9__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: Rubka
3
- Version: 0.1.2
3
+ Version: 1.2.9
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rubka
3
- Version: 0.1.2
3
+ Version: 1.2.9
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
@@ -8,8 +8,11 @@ Rubka.egg-info/top_level.txt
8
8
  rubka/__init__.py
9
9
  rubka/api.py
10
10
  rubka/config.py
11
+ rubka/context.py
11
12
  rubka/decorators.py
12
13
  rubka/exceptions.py
14
+ rubka/jobs.py
13
15
  rubka/keyboards.py
16
+ rubka/keypad.py
14
17
  rubka/logger.py
15
18
  rubka/utils.py
@@ -0,0 +1,363 @@
1
+ import requests
2
+ from typing import List, Optional, Dict, Any, Literal
3
+ from .exceptions import APIRequestError
4
+ from .logger import logger
5
+ from typing import Callable
6
+ from .context import Message,InlineMessage
7
+ API_URL = "https://botapi.rubika.ir/v3"
8
+ import sys
9
+ import subprocess
10
+ import requests
11
+ def install_package(package_name):
12
+ try:
13
+ subprocess.check_call([sys.executable, "-m", "pip", "install", package_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
14
+ return True
15
+ except Exception:return False
16
+
17
+ def get_importlib_metadata():
18
+ try:
19
+ from importlib.metadata import version, PackageNotFoundError
20
+ return version, PackageNotFoundError
21
+ except ImportError:
22
+ if install_package("importlib-metadata"):
23
+ try:
24
+ from importlib_metadata import version, PackageNotFoundError
25
+ return version, PackageNotFoundError
26
+ except ImportError:
27
+ return None, None
28
+ return None, None
29
+
30
+ version, PackageNotFoundError = get_importlib_metadata()
31
+ def get_installed_version(package_name: str) -> str:
32
+ if version is None:return "unknown"
33
+ try:
34
+ return version(package_name)
35
+ except PackageNotFoundError:
36
+ return None
37
+ def get_latest_version(package_name: str) -> str:
38
+ url = f"https://pypi.org/pypi/{package_name}/json"
39
+ try:
40
+ resp = requests.get(url, timeout=5)
41
+ resp.raise_for_status()
42
+ data = resp.json()
43
+ return data["info"]["version"]
44
+ except Exception:return None
45
+ def check_rubka_version():
46
+ package_name = "rubka"
47
+ installed_version = get_installed_version(package_name)
48
+ if installed_version is None:return
49
+ latest_version = get_latest_version(package_name)
50
+ if latest_version is None:return
51
+ if installed_version != latest_version:
52
+ print(f"\n\n⚠️ WARNING: Your installed version of '{package_name}' is outdated!")
53
+ print(f"Installed version: {installed_version}")
54
+ print(f"Latest version: {latest_version}")
55
+ print(f"Please update it using:\n\npip install --upgrade {package_name}\n")
56
+
57
+ check_rubka_version()
58
+ class Robot:
59
+ """
60
+ Main class to interact with Rubika Bot API.
61
+ Initialized with bot token.
62
+ """
63
+
64
+ def __init__(self, token: str):
65
+ self.token = token
66
+ self._offset_id = None
67
+ self.session = requests.Session()
68
+ self.sessions: Dict[str, Dict[str, Any]] = {}
69
+ self._callback_handler = None
70
+ self._message_handler = None
71
+ self._inline_query_handler = None
72
+
73
+
74
+ logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
75
+
76
+ def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
77
+ url = f"{API_URL}/{self.token}/{method}"
78
+ try:
79
+ response = self.session.post(url, json=data, timeout=10)
80
+ response.raise_for_status()
81
+ try:
82
+ json_resp = response.json()
83
+ except ValueError:
84
+ logger.error(f"Invalid JSON response from {method}: {response.text}")
85
+ raise APIRequestError(f"Invalid JSON response: {response.text}")
86
+ if method != "getUpdates":logger.debug(f"API Response from {method}: {json_resp}")
87
+
88
+ return json_resp
89
+ except requests.RequestException as e:
90
+ logger.error(f"API request failed: {e}")
91
+ raise APIRequestError(f"API request failed: {e}") from e
92
+
93
+
94
+ def get_me(self) -> Dict[str, Any]:
95
+ """Get info about the bot itself."""
96
+ return self._post("getMe", {})
97
+ def on_message(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
98
+ def decorator(func: Callable[[Any, Message], None]):
99
+ self._message_handler = {
100
+ "func": func,
101
+ "filters": filters,
102
+ "commands": commands
103
+ }
104
+ return func
105
+ return decorator
106
+
107
+
108
+ def on_inline_query(self):
109
+ def decorator(func: Callable[[Any, InlineMessage], None]):
110
+ self._inline_query_handler = func
111
+ return func
112
+ return decorator
113
+
114
+
115
+
116
+ def _process_update(self, update: Dict[str, Any]):
117
+ if update.get('type') == 'ReceiveQuery':
118
+ msg = update.get("inline_message", {})
119
+ if self._inline_query_handler:
120
+ context = InlineMessage(bot=self, raw_data=msg)
121
+ self._inline_query_handler(self, context)
122
+ if update.get('type') == 'NewMessage':
123
+ msg = update.get('new_message', {})
124
+ chat_id = update.get('chat_id')
125
+ message_id = msg.get('message_id')
126
+ sender_id = msg.get('sender_id')
127
+ text = msg.get('text')
128
+ try:
129
+ import time
130
+ if msg.get("time") and (time.time() - float(msg["time"])) > 10:return
131
+ except Exception as e:return
132
+ if self._message_handler:
133
+ handler = self._message_handler
134
+ context = Message(bot=self, chat_id=chat_id, message_id=message_id, sender_id=sender_id, text=text, raw_data=msg)
135
+ if handler["commands"]:
136
+ if not context.text or not context.text.startswith("/"):
137
+ return
138
+ parts = context.text.split()
139
+ cmd = parts[0][1:]
140
+ if cmd not in handler["commands"]:
141
+ return
142
+ context.args = parts[1:]
143
+
144
+ if handler["filters"]:
145
+ if not handler["filters"](context):
146
+ return
147
+
148
+ handler["func"](self, context)
149
+
150
+ elif update.get('type') == 'ReceiveQuery':
151
+ msg = update.get("inline_message", {})
152
+ chat_id = msg.get("chat_id")
153
+ message_id = msg.get("message_id")
154
+ sender_id = msg.get("sender_id")
155
+ text = msg.get("text")
156
+
157
+ if hasattr(self, "_callback_handler"):
158
+ context = Message(bot=self, chat_id=chat_id, message_id=message_id, sender_id=sender_id, text=text, raw_data=msg)
159
+ self._callback_handler(self, context)
160
+
161
+
162
+ def run(self):
163
+ print("Bot started running...")
164
+ if self._offset_id is None:
165
+ try:
166
+ latest = self.get_updates(limit=100)
167
+ if latest and latest.get("data") and latest["data"].get("updates"):
168
+ updates = latest["data"]["updates"]
169
+ last_update = updates[-1]
170
+ self._offset_id = latest["data"].get("next_offset_id")
171
+ print(f"Offset initialized to: {self._offset_id}")
172
+ else:
173
+ print("No updates found.")
174
+ except Exception as e:
175
+ print(f"Failed to fetch latest message: {e}")
176
+
177
+ while True:
178
+ try:
179
+ updates = self.get_updates(offset_id=self._offset_id, limit=1)
180
+ if updates and updates.get("data"):
181
+ for update in updates["data"].get("updates", []):
182
+ self._process_update(update)
183
+ self._offset_id = updates["data"].get("next_offset_id", self._offset_id)
184
+ except Exception as e:
185
+ print(f"Error in run loop: {e}")
186
+
187
+ def send_message(
188
+ self,
189
+ chat_id: str,
190
+ text: str,
191
+ chat_keypad: Optional[Dict[str, Any]] = None,
192
+ inline_keypad: Optional[Dict[str, Any]] = None,
193
+ disable_notification: bool = False,
194
+ reply_to_message_id: Optional[str] = None,
195
+ chat_keypad_type: Optional[Literal["New", "Removed"]] = None
196
+ ) -> Dict[str, Any]:
197
+ """
198
+ Send a text message to a chat.
199
+ """
200
+ payload = {
201
+ "chat_id": chat_id,
202
+ "text": text,
203
+ "disable_notification": disable_notification
204
+ }
205
+ if chat_keypad:
206
+ payload["chat_keypad"] = chat_keypad
207
+ if inline_keypad:
208
+ payload["inline_keypad"] = inline_keypad
209
+ if reply_to_message_id:
210
+ payload["reply_to_message_id"] = reply_to_message_id
211
+ if chat_keypad_type:
212
+ payload["chat_keypad_type"] = chat_keypad_type
213
+
214
+ return self._post("sendMessage", payload)
215
+
216
+ def send_poll(
217
+ self,
218
+ chat_id: str,
219
+ question: str,
220
+ options: List[str]
221
+ ) -> Dict[str, Any]:
222
+ """
223
+ Send a poll to a chat.
224
+ """
225
+ return self._post("sendPoll", {
226
+ "chat_id": chat_id,
227
+ "question": question,
228
+ "options": options
229
+ })
230
+
231
+ def send_location(
232
+ self,
233
+ chat_id: str,
234
+ latitude: str,
235
+ longitude: str,
236
+ disable_notification: bool = False,
237
+ inline_keypad: Optional[Dict[str, Any]] = None,
238
+ reply_to_message_id: Optional[str] = None,
239
+ chat_keypad_type: Optional[Literal["New", "Removed"]] = None
240
+ ) -> Dict[str, Any]:
241
+ """
242
+ Send a location to a chat.
243
+ """
244
+ payload = {
245
+ "chat_id": chat_id,
246
+ "latitude": latitude,
247
+ "longitude": longitude,
248
+ "disable_notification": disable_notification,
249
+ "inline_keypad": inline_keypad,
250
+ "reply_to_message_id": reply_to_message_id,
251
+ "chat_keypad_type": chat_keypad_type
252
+ }
253
+ # Remove None values
254
+ payload = {k: v for k, v in payload.items() if v is not None}
255
+ return self._post("sendLocation", payload)
256
+
257
+ def send_contact(
258
+ self,
259
+ chat_id: str,
260
+ first_name: str,
261
+ last_name: str,
262
+ phone_number: str
263
+ ) -> Dict[str, Any]:
264
+ """
265
+ Send a contact to a chat.
266
+ """
267
+ return self._post("sendContact", {
268
+ "chat_id": chat_id,
269
+ "first_name": first_name,
270
+ "last_name": last_name,
271
+ "phone_number": phone_number
272
+ })
273
+
274
+ def get_chat(self, chat_id: str) -> Dict[str, Any]:
275
+ """Get chat info."""
276
+ return self._post("getChat", {"chat_id": chat_id})
277
+
278
+ def get_updates(
279
+ self,
280
+ offset_id: Optional[str] = None,
281
+ limit: Optional[int] = None
282
+ ) -> Dict[str, Any]:
283
+ """Get updates."""
284
+ data = {}
285
+ if offset_id:
286
+ data["offset_id"] = offset_id
287
+ if limit:
288
+ data["limit"] = limit
289
+ return self._post("getUpdates", data)
290
+
291
+ def forward_message(
292
+ self,
293
+ from_chat_id: str,
294
+ message_id: str,
295
+ to_chat_id: str,
296
+ disable_notification: bool = False
297
+ ) -> Dict[str, Any]:
298
+ """Forward a message from one chat to another."""
299
+ return self._post("forwardMessage", {
300
+ "from_chat_id": from_chat_id,
301
+ "message_id": message_id,
302
+ "to_chat_id": to_chat_id,
303
+ "disable_notification": disable_notification
304
+ })
305
+
306
+ def edit_message_text(
307
+ self,
308
+ chat_id: str,
309
+ message_id: str,
310
+ text: str
311
+ ) -> Dict[str, Any]:
312
+ """Edit text of an existing message."""
313
+ return self._post("editMessageText", {
314
+ "chat_id": chat_id,
315
+ "message_id": message_id,
316
+ "text": text
317
+ })
318
+
319
+ def edit_inline_keypad(
320
+ self,
321
+ chat_id: str,
322
+ message_id: str,
323
+ inline_keypad: Dict[str, Any]
324
+ ) -> Dict[str, Any]:
325
+ """Edit inline keypad of a message."""
326
+ return self._post("editInlineKeypad", {
327
+ "chat_id": chat_id,
328
+ "message_id": message_id,
329
+ "inline_keypad": inline_keypad
330
+ })
331
+
332
+ def delete_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
333
+ """Delete a message from chat."""
334
+ return self._post("deleteMessage", {
335
+ "chat_id": chat_id,
336
+ "message_id": message_id
337
+ })
338
+
339
+ def set_commands(self, bot_commands: List[Dict[str, str]]) -> Dict[str, Any]:
340
+ """Set bot commands."""
341
+ return self._post("setCommands", {"bot_commands": bot_commands})
342
+
343
+ def update_bot_endpoint(self, url: str, type: str) -> Dict[str, Any]:
344
+ """Update bot endpoint (Webhook or Polling)."""
345
+ return self._post("updateBotEndpoints", {
346
+ "url": url,
347
+ "type": type
348
+ })
349
+
350
+ def remove_keypad(self, chat_id: str) -> Dict[str, Any]:
351
+ """Remove chat keypad."""
352
+ return self._post("editChatKeypad", {
353
+ "chat_id": chat_id,
354
+ "chat_keypad_type": "Removed"
355
+ })
356
+
357
+ def edit_chat_keypad(self, chat_id: str, chat_keypad: Dict[str, Any]) -> Dict[str, Any]:
358
+ """Edit or add new chat keypad."""
359
+ return self._post("editChatKeypad", {
360
+ "chat_id": chat_id,
361
+ "chat_keypad_type": "New",
362
+ "chat_keypad": chat_keypad
363
+ })
@@ -0,0 +1,328 @@
1
+ from typing import Any, Dict, List,Optional
2
+
3
+ class File:
4
+ def __init__(self, data: dict):
5
+ self.file_id: str = data.get("file_id")
6
+ self.file_name: str = data.get("file_name")
7
+ self.size: str = data.get("size")
8
+
9
+
10
+ class Sticker:
11
+ def __init__(self, data: dict):
12
+ self.sticker_id: str = data.get("sticker_id")
13
+ self.emoji_character: str = data.get("emoji_character")
14
+ self.file = File(data.get("file", {}))
15
+
16
+
17
+ # =========================
18
+ # Poll
19
+ # =========================
20
+ class PollStatus:
21
+ def __init__(self, data: dict):
22
+ self.state: str = data.get("state")
23
+ self.selection_index: int = data.get("selection_index")
24
+ self.percent_vote_options: List[int] = data.get("percent_vote_options", [])
25
+ self.total_vote: int = data.get("total_vote")
26
+ self.show_total_votes: bool = data.get("show_total_votes")
27
+
28
+
29
+ class Poll:
30
+ def __init__(self, data: dict):
31
+ self.question: str = data.get("question")
32
+ self.options: List[str] = data.get("options", [])
33
+ self.poll_status = PollStatus(data.get("poll_status", {}))
34
+
35
+
36
+ # =========================
37
+ # Location & Contact & ForwardedFrom
38
+ # =========================
39
+ class Location:
40
+ def __init__(self, data: dict):
41
+ self.latitude: str = data.get("latitude")
42
+ self.longitude: str = data.get("longitude")
43
+
44
+
45
+ class LiveLocation:
46
+ def __init__(self, data: dict):
47
+ self.start_time: str = data.get("start_time")
48
+ self.live_period: int = data.get("live_period")
49
+ self.current_location = Location(data.get("current_location", {}))
50
+ self.user_id: str = data.get("user_id")
51
+ self.status: str = data.get("status")
52
+ self.last_update_time: str = data.get("last_update_time")
53
+
54
+
55
+ class ContactMessage:
56
+ def __init__(self, data: dict):
57
+ self.phone_number: str = data.get("phone_number")
58
+ self.first_name: str = data.get("first_name")
59
+ self.last_name: str = data.get("last_name")
60
+
61
+
62
+ class ForwardedFrom:
63
+ def __init__(self, data: dict):
64
+ self.type_from: str = data.get("type_from")
65
+ self.message_id: str = data.get("message_id")
66
+ self.from_chat_id: str = data.get("from_chat_id")
67
+ self.from_sender_id: str = data.get("from_sender_id")
68
+
69
+
70
+ # =========================
71
+ # AuxData
72
+ # =========================
73
+ class AuxData:
74
+ def __init__(self, data: dict):
75
+ self.start_id: str = data.get("start_id")
76
+ self.button_id: str = data.get("button_id")
77
+
78
+
79
+ # =========================
80
+ # Button Models
81
+ # =========================
82
+ class ButtonTextbox:
83
+ def __init__(self, data: dict):
84
+ self.type_line: str = data.get("type_line")
85
+ self.type_keypad: str = data.get("type_keypad")
86
+ self.place_holder: Optional[str] = data.get("place_holder")
87
+ self.title: Optional[str] = data.get("title")
88
+ self.default_value: Optional[str] = data.get("default_value")
89
+
90
+
91
+ class ButtonNumberPicker:
92
+ def __init__(self, data: dict):
93
+ self.min_value: str = data.get("min_value")
94
+ self.max_value: str = data.get("max_value")
95
+ self.default_value: Optional[str] = data.get("default_value")
96
+ self.title: str = data.get("title")
97
+
98
+
99
+ class ButtonStringPicker:
100
+ def __init__(self, data: dict):
101
+ self.items: List[str] = data.get("items", [])
102
+ self.default_value: Optional[str] = data.get("default_value")
103
+ self.title: Optional[str] = data.get("title")
104
+
105
+
106
+ class ButtonCalendar:
107
+ def __init__(self, data: dict):
108
+ self.default_value: Optional[str] = data.get("default_value")
109
+ self.type: str = data.get("type")
110
+ self.min_year: str = data.get("min_year")
111
+ self.max_year: str = data.get("max_year")
112
+ self.title: str = data.get("title")
113
+
114
+
115
+ class ButtonLocation:
116
+ def __init__(self, data: dict):
117
+ self.default_pointer_location = Location(data.get("default_pointer_location", {}))
118
+ self.default_map_location = Location(data.get("default_map_location", {}))
119
+ self.type: str = data.get("type")
120
+ self.title: Optional[str] = data.get("title")
121
+ self.location_image_url: str = data.get("location_image_url")
122
+
123
+
124
+ class ButtonSelectionItem:
125
+ def __init__(self, data: dict):
126
+ self.text: str = data.get("text")
127
+ self.image_url: str = data.get("image_url")
128
+ self.type: str = data.get("type")
129
+
130
+
131
+ class ButtonSelection:
132
+ def __init__(self, data: dict):
133
+ self.selection_id: str = data.get("selection_id")
134
+ self.search_type: str = data.get("search_type")
135
+ self.get_type: str = data.get("get_type")
136
+ self.items: List[ButtonSelectionItem] = [ButtonSelectionItem(i) for i in data.get("items", [])]
137
+ self.is_multi_selection: bool = data.get("is_multi_selection")
138
+ self.columns_count: str = data.get("columns_count")
139
+ self.title: str = data.get("title")
140
+
141
+
142
+ class Button:
143
+ def __init__(self, data: dict):
144
+ self.id: str = data.get("id")
145
+ self.type: str = data.get("type")
146
+ self.button_text: str = data.get("button_text")
147
+ self.button_selection = ButtonSelection(data.get("button_selection", {})) if "button_selection" in data else None
148
+ self.button_calendar = ButtonCalendar(data.get("button_calendar", {})) if "button_calendar" in data else None
149
+ self.button_number_picker = ButtonNumberPicker(data.get("button_number_picker", {})) if "button_number_picker" in data else None
150
+ self.button_string_picker = ButtonStringPicker(data.get("button_string_picker", {})) if "button_string_picker" in data else None
151
+ self.button_location = ButtonLocation(data.get("button_location", {})) if "button_location" in data else None
152
+ self.button_textbox = ButtonTextbox(data.get("button_textbox", {})) if "button_textbox" in data else None
153
+
154
+
155
+ class KeypadRow:
156
+ def __init__(self, data: dict):
157
+ self.buttons: List[Button] = [Button(btn) for btn in data.get("buttons", [])]
158
+
159
+
160
+ class Keypad:
161
+ def __init__(self, data: dict):
162
+ self.rows: List[KeypadRow] = [KeypadRow(r) for r in data.get("rows", [])]
163
+ self.resize_keyboard: bool = data.get("resize_keyboard", False)
164
+ self.on_time_keyboard: bool = data.get("on_time_keyboard", False)
165
+
166
+
167
+ class Chat:
168
+ def __init__(self, data: dict):
169
+ self.chat_id: str = data.get("chat_id")
170
+ self.chat_type: str = data.get("chat_type")
171
+ self.user_id: str = data.get("user_id")
172
+ self.first_name: str = data.get("first_name")
173
+ self.last_name: str = data.get("last_name")
174
+ self.title: str = data.get("title")
175
+ self.username: str = data.get("username")
176
+
177
+
178
+ class Bot:
179
+ def __init__(self, data: dict):
180
+ self.bot_id: str = data.get("bot_id")
181
+ self.bot_title: str = data.get("bot_title")
182
+ self.avatar = File(data.get("avatar", {}))
183
+ self.description: str = data.get("description")
184
+ self.username: str = data.get("username")
185
+ self.start_message: str = data.get("start_message")
186
+ self.share_url: str = data.get("share_url")
187
+ class Message:
188
+ def __init__(self, bot, chat_id, message_id, sender_id, text=None, raw_data=None):
189
+ self.bot = bot
190
+ self.chat_id = chat_id
191
+ self.raw_data = raw_data or {}
192
+ self.message_id: str = self.raw_data.get("message_id", message_id)
193
+ self.text: str = self.raw_data.get("text", text)
194
+ self.sender_id: str = self.raw_data.get("sender_id", sender_id)
195
+ self.time: str = self.raw_data.get("time")
196
+ self.is_edited: bool = self.raw_data.get("is_edited", False)
197
+ self.sender_type: str = self.raw_data.get("sender_type")
198
+
199
+ self.args = []
200
+ self.reply_to_message_id: Optional[str] = self.raw_data.get("reply_to_message_id")
201
+ self.forwarded_from = ForwardedFrom(self.raw_data["forwarded_from"]) if "forwarded_from" in self.raw_data else None
202
+ self.file = File(self.raw_data["file"]) if "file" in self.raw_data else None
203
+ self.sticker = Sticker(self.raw_data["sticker"]) if "sticker" in self.raw_data else None
204
+ self.contact_message = ContactMessage(self.raw_data["contact_message"]) if "contact_message" in self.raw_data else None
205
+ self.poll = Poll(self.raw_data["poll"]) if "poll" in self.raw_data else None
206
+ self.location = Location(self.raw_data["location"]) if "location" in self.raw_data else None
207
+ self.live_location = LiveLocation(self.raw_data["live_location"]) if "live_location" in self.raw_data else None
208
+ self.aux_data = AuxData(self.raw_data["aux_data"]) if "aux_data" in self.raw_data else None
209
+
210
+ @property
211
+ def session(self):
212
+ if self.chat_id not in self.bot.sessions:
213
+ self.bot.sessions[self.chat_id] = {}
214
+ return self.bot.sessions[self.chat_id]
215
+ def reply(self, text: str, **kwargs):
216
+ return self.bot.send_message(
217
+ self.chat_id,
218
+ text,
219
+ reply_to_message_id=self.message_id,
220
+ **kwargs
221
+ )
222
+
223
+ def reply_poll(self, question: str, options: List[str], **kwargs) -> Dict[str, Any]:
224
+ return self.bot._post("sendPoll", {
225
+ "chat_id": self.chat_id,
226
+ "question": question,
227
+ "options": options,
228
+ "reply_to_message_id": self.message_id,
229
+ **kwargs
230
+ })
231
+
232
+ def reply_location(self, latitude: str, longitude: str, **kwargs) -> Dict[str, Any]:
233
+ return self.bot.send_location(
234
+ chat_id=self.chat_id,
235
+ latitude=latitude,
236
+ longitude=longitude,
237
+ reply_to_message_id=self.message_id,
238
+ **kwargs
239
+ )
240
+
241
+ def reply_contact(self, first_name: str, last_name: str, phone_number: str, **kwargs) -> Dict[str, Any]:
242
+ return self.bot.send_contact(
243
+ chat_id=self.chat_id,
244
+ first_name=first_name,
245
+ last_name=last_name,
246
+ phone_number=phone_number,
247
+ reply_to_message_id=self.message_id,
248
+ **kwargs
249
+ )
250
+
251
+ def reply_keypad(self, text: str, keypad: Dict[str, Any], **kwargs) -> Dict[str, Any]:
252
+ return self.bot.send_message(
253
+ chat_id=self.chat_id,
254
+ text=text,
255
+ chat_keypad_type="New",
256
+ chat_keypad=keypad,
257
+ reply_to_message_id=self.message_id,
258
+ **kwargs
259
+ )
260
+
261
+ def reply_inline(self, text: str, inline_keypad: Dict[str, Any], **kwargs) -> Dict[str, Any]:
262
+ return self.bot.send_message(
263
+ chat_id=self.chat_id,
264
+ text=text,
265
+ inline_keypad=inline_keypad,
266
+ reply_to_message_id=self.message_id,
267
+ **kwargs
268
+ )
269
+
270
+ def reply_sticker(self, sticker_id: str, **kwargs) -> Dict[str, Any]:
271
+ return self.bot._post("sendSticker", {
272
+ "chat_id": self.chat_id,
273
+ "sticker_id": sticker_id,
274
+ "reply_to_message_id": self.message_id,
275
+ **kwargs
276
+ })
277
+
278
+ def reply_file(self, file_id: str, **kwargs) -> Dict[str, Any]:
279
+ return self.bot._post("sendFile", {
280
+ "chat_id": self.chat_id,
281
+ "file_id": file_id,
282
+ "reply_to_message_id": self.message_id,
283
+ **kwargs
284
+ })
285
+
286
+ def edit(self, new_text: str) -> Dict[str, Any]:
287
+ return self.bot.edit_message_text(
288
+ chat_id=self.chat_id,
289
+ message_id=self.message_id,
290
+ text=new_text
291
+ )
292
+
293
+ def delete(self) -> Dict[str, Any]:
294
+ return self.bot.delete_message(
295
+ chat_id=self.chat_id,
296
+ message_id=self.message_id
297
+ )
298
+ class InlineMessage:
299
+ def __init__(self, bot, raw_data: dict):
300
+ self.bot = bot
301
+ self.raw_data = raw_data
302
+
303
+ self.chat_id: str = raw_data.get("chat_id")
304
+ self.message_id: str = raw_data.get("message_id")
305
+ self.sender_id: str = raw_data.get("sender_id")
306
+ self.text: str = raw_data.get("text")
307
+ self.aux_data = AuxData(raw_data.get("aux_data", {})) if "aux_data" in raw_data else None
308
+
309
+ def reply(self, text: str, **kwargs):
310
+ return self.bot.send_message(
311
+ chat_id=self.chat_id,
312
+ text=text,
313
+ reply_to_message_id=self.message_id,
314
+ **kwargs
315
+ )
316
+
317
+ def edit(self, new_text: str):
318
+ return self.bot.edit_message_text(
319
+ chat_id=self.chat_id,
320
+ message_id=self.message_id,
321
+ text=new_text
322
+ )
323
+
324
+ def delete(self):
325
+ return self.bot.delete_message(
326
+ chat_id=self.chat_id,
327
+ message_id=self.message_id
328
+ )
@@ -1,4 +1,3 @@
1
1
  class APIRequestError(Exception):
2
2
  """Raised when an API request fails."""
3
- raise Exception
4
3
  pass
@@ -0,0 +1,15 @@
1
+ import threading
2
+ import time
3
+ from typing import Callable
4
+
5
+ class Job:
6
+ def __init__(self, delay: int, callback: Callable):
7
+ self.delay = delay
8
+ self.callback = callback
9
+ thread = threading.Thread(target=self.run)
10
+ thread.daemon = True
11
+ thread.start()
12
+
13
+ def run(self):
14
+ time.sleep(self.delay)
15
+ self.callback()
@@ -0,0 +1,48 @@
1
+ from typing import Dict
2
+
3
+ class InlineBuilder:
4
+ def __init__(self):
5
+ self.rows = []
6
+
7
+ def row(self, *buttons: Dict[str, str]):
8
+ self.rows.append({"buttons": list(buttons)})
9
+ return self
10
+
11
+ def button(self, id: str, text: str, type: str = "Simple") -> Dict[str, str]:
12
+ return {"id": id, "type": type, "button_text": text}
13
+
14
+ def build(self):
15
+ return {"rows": self.rows}
16
+ from typing import List, Dict, Optional
17
+
18
+ class ChatKeypadBuilder:
19
+ def __init__(self):
20
+ self.rows: List[Dict[str, List[Dict[str, str]]]] = []
21
+
22
+ def row(self, *buttons: Dict[str, str]) -> "ChatKeypadBuilder":
23
+ """
24
+ یک ردیف دکمه به کی‌پد اضافه می‌کند.
25
+ ورودی: چند دیکشنری که نماینده دکمه‌ها هستند.
26
+ """
27
+ self.rows.append({"buttons": list(buttons)})
28
+ return self
29
+
30
+ def button(self, id: str, text: str, type: str = "Simple") -> Dict[str, str]:
31
+ """
32
+ دیکشنری یک دکمه می‌سازد.
33
+ """
34
+ return {"id": id, "type": type, "button_text": text}
35
+
36
+ def build(
37
+ self,
38
+ resize_keyboard: bool = True,
39
+ on_time_keyboard: bool = False
40
+ ) -> Dict[str, object]:
41
+ """
42
+ ساختار نهایی chat_keypad را می‌سازد.
43
+ """
44
+ return {
45
+ "rows": self.rows,
46
+ "resize_keyboard": resize_keyboard,
47
+ "on_time_keyboard": on_time_keyboard
48
+ }
@@ -1,10 +1,10 @@
1
1
  import logging
2
2
 
3
3
  logger = logging.getLogger("rubka")
4
- logger.setLevel(logging.DEBUG)
4
+ logger.setLevel(logging.ERROR)
5
5
 
6
6
  ch = logging.StreamHandler()
7
- ch.setLevel(logging.DEBUG)
7
+ ch.setLevel(logging.ERROR)
8
8
 
9
9
  formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
10
10
  ch.setFormatter(formatter)
@@ -8,7 +8,7 @@ except FileNotFoundError:
8
8
 
9
9
  setup(
10
10
  name='Rubka',
11
- version='0.1.2',
11
+ version='1.2.9',
12
12
  description='A Python library for interacting with Rubika Bot API.',
13
13
  long_description=long_description,
14
14
  long_description_content_type='text/markdown',
rubka-0.1.2/rubka/api.py DELETED
@@ -1,211 +0,0 @@
1
- import requests
2
- from typing import List, Optional, Dict, Any, Literal
3
- from .exceptions import APIRequestError
4
- from .logger import logger
5
-
6
- API_URL = "https://botapi.rubika.ir/v3"
7
-
8
- class Robot:
9
- """
10
- Main class to interact with Rubika Bot API.
11
- Initialized with bot token.
12
- """
13
-
14
- def __init__(self, token: str):
15
- self.token = token
16
- self.session = requests.Session()
17
- logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
18
-
19
- def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
20
- url = f"{API_URL}/{self.token}/{method}"
21
- try:
22
- response = self.session.post(url, json=data, timeout=10)
23
- response.raise_for_status()
24
- json_resp = response.json()
25
- logger.debug(f"API Response from {method}: {json_resp}")
26
- return json_resp
27
- except requests.RequestException as e:
28
- logger.error(f"API request failed: {e}")
29
- raise APIRequestError(f"API request failed: {e}") from e
30
-
31
- def get_me(self) -> Dict[str, Any]:
32
- """Get info about the bot itself."""
33
- return self._post("getMe", {})
34
-
35
- def send_message(
36
- self,
37
- chat_id: str,
38
- text: str,
39
- chat_keypad: Optional[Dict[str, Any]] = None,
40
- inline_keypad: Optional[Dict[str, Any]] = None,
41
- disable_notification: bool = False,
42
- reply_to_message_id: Optional[str] = None,
43
- chat_keypad_type: Optional[Literal["New", "Removed"]] = None
44
- ) -> Dict[str, Any]:
45
- """
46
- Send a text message to a chat.
47
- """
48
- payload = {
49
- "chat_id": chat_id,
50
- "text": text,
51
- "disable_notification": disable_notification
52
- }
53
- if chat_keypad:
54
- payload["chat_keypad"] = chat_keypad
55
- if inline_keypad:
56
- payload["inline_keypad"] = inline_keypad
57
- if reply_to_message_id:
58
- payload["reply_to_message_id"] = reply_to_message_id
59
- if chat_keypad_type:
60
- payload["chat_keypad_type"] = chat_keypad_type
61
-
62
- return self._post("sendMessage", payload)
63
-
64
- def send_poll(
65
- self,
66
- chat_id: str,
67
- question: str,
68
- options: List[str]
69
- ) -> Dict[str, Any]:
70
- """
71
- Send a poll to a chat.
72
- """
73
- return self._post("sendPoll", {
74
- "chat_id": chat_id,
75
- "question": question,
76
- "options": options
77
- })
78
-
79
- def send_location(
80
- self,
81
- chat_id: str,
82
- latitude: str,
83
- longitude: str,
84
- disable_notification: bool = False,
85
- inline_keypad: Optional[Dict[str, Any]] = None,
86
- reply_to_message_id: Optional[str] = None,
87
- chat_keypad_type: Optional[Literal["New", "Removed"]] = None
88
- ) -> Dict[str, Any]:
89
- """
90
- Send a location to a chat.
91
- """
92
- payload = {
93
- "chat_id": chat_id,
94
- "latitude": latitude,
95
- "longitude": longitude,
96
- "disable_notification": disable_notification,
97
- "inline_keypad": inline_keypad,
98
- "reply_to_message_id": reply_to_message_id,
99
- "chat_keypad_type": chat_keypad_type
100
- }
101
- # Remove None values
102
- payload = {k: v for k, v in payload.items() if v is not None}
103
- return self._post("sendLocation", payload)
104
-
105
- def send_contact(
106
- self,
107
- chat_id: str,
108
- first_name: str,
109
- last_name: str,
110
- phone_number: str
111
- ) -> Dict[str, Any]:
112
- """
113
- Send a contact to a chat.
114
- """
115
- return self._post("sendContact", {
116
- "chat_id": chat_id,
117
- "first_name": first_name,
118
- "last_name": last_name,
119
- "phone_number": phone_number
120
- })
121
-
122
- def get_chat(self, chat_id: str) -> Dict[str, Any]:
123
- """Get chat info."""
124
- return self._post("getChat", {"chat_id": chat_id})
125
-
126
- def get_updates(
127
- self,
128
- offset_id: Optional[str] = None,
129
- limit: Optional[int] = None
130
- ) -> Dict[str, Any]:
131
- """Get updates."""
132
- data = {}
133
- if offset_id:
134
- data["offset_id"] = offset_id
135
- if limit:
136
- data["limit"] = limit
137
- return self._post("getUpdates", data)
138
-
139
- def forward_message(
140
- self,
141
- from_chat_id: str,
142
- message_id: str,
143
- to_chat_id: str,
144
- disable_notification: bool = False
145
- ) -> Dict[str, Any]:
146
- """Forward a message from one chat to another."""
147
- return self._post("forwardMessage", {
148
- "from_chat_id": from_chat_id,
149
- "message_id": message_id,
150
- "to_chat_id": to_chat_id,
151
- "disable_notification": disable_notification
152
- })
153
-
154
- def edit_message_text(
155
- self,
156
- chat_id: str,
157
- message_id: str,
158
- text: str
159
- ) -> Dict[str, Any]:
160
- """Edit text of an existing message."""
161
- return self._post("editMessageText", {
162
- "chat_id": chat_id,
163
- "message_id": message_id,
164
- "text": text
165
- })
166
-
167
- def edit_inline_keypad(
168
- self,
169
- chat_id: str,
170
- message_id: str,
171
- inline_keypad: Dict[str, Any]
172
- ) -> Dict[str, Any]:
173
- """Edit inline keypad of a message."""
174
- return self._post("editInlineKeypad", {
175
- "chat_id": chat_id,
176
- "message_id": message_id,
177
- "inline_keypad": inline_keypad
178
- })
179
-
180
- def delete_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
181
- """Delete a message from chat."""
182
- return self._post("deleteMessage", {
183
- "chat_id": chat_id,
184
- "message_id": message_id
185
- })
186
-
187
- def set_commands(self, bot_commands: List[Dict[str, str]]) -> Dict[str, Any]:
188
- """Set bot commands."""
189
- return self._post("setCommands", {"bot_commands": bot_commands})
190
-
191
- def update_bot_endpoint(self, url: str, type: str) -> Dict[str, Any]:
192
- """Update bot endpoint (Webhook or Polling)."""
193
- return self._post("updateBotEndpoints", {
194
- "url": url,
195
- "type": type
196
- })
197
-
198
- def remove_keypad(self, chat_id: str) -> Dict[str, Any]:
199
- """Remove chat keypad."""
200
- return self._post("editChatKeypad", {
201
- "chat_id": chat_id,
202
- "chat_keypad_type": "Removed"
203
- })
204
-
205
- def edit_chat_keypad(self, chat_id: str, chat_keypad: Dict[str, Any]) -> Dict[str, Any]:
206
- """Edit or add new chat keypad."""
207
- return self._post("editChatKeypad", {
208
- "chat_id": chat_id,
209
- "chat_keypad_type": "New",
210
- "chat_keypad": chat_keypad
211
- })
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes