PyRubikaBotAPI 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alireza Sadeghian
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. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.1
2
+ Name: PyRubikaBotAPI
3
+ Version: 0.0.1
4
+ Summary: A Python library to send messages and files via Eitaa API
5
+ Home-page: https://github.com/alireza-sadeghian/EitaaSender
6
+ Author: Alireza Sadeghian
7
+ Author-email: alireza.amid110@gmail.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENCE
15
+ Requires-Dist: requests
16
+
17
+ # PyRubikaBotAPI
18
+
19
+ یک کتابخانه برای ساخت ربات‌های روبیکا با پایتون
20
+
21
+ ## نصب
22
+ ```bash
23
+ pip install PyRubikaBotAPI
24
+
25
+ ```
26
+
27
+ ## مثال ساده
28
+
29
+ ```python
30
+ from rubibot import RubiBot
31
+
32
+ bot = RubiBot("YOUR_TOKEN")
33
+
34
+ @bot.message_handler(commands=["start"])
35
+ def start(message):
36
+ bot.send_message(message.chat_id, "سلام! خوش آمدید!")
37
+
38
+ bot.polling()
39
+ ```
40
+
41
+ ## مثال پیشرفته تر
42
+ ```py
43
+ @bot.message_handler(content_types=['file'])
44
+ def handle(msg):
45
+ file_id = msg.file.id
46
+ file_url = bot.get_file(file_id)
47
+ file = bot.download_file(file_url)
48
+ with open('file.format', 'wb') as f
49
+ f.write(file)
50
+ # استفاده از فایل ارسالی
51
+ ```
52
+
53
+ ## مستندات:
54
+
55
+ Rubika: https://rubika.ir/pyrubikabotapi
56
+ GitHub: https://github.com/alireza-sadeghian/PyRubikaBotAPI
@@ -0,0 +1,8 @@
1
+ rubibot/__init__.py,sha256=zTKcgvIX-ZpyfuNMfw3gUq7lBsVJFgjjzrhHBfX5te0,31518
2
+ rubibot/helper.py,sha256=al6HhY6tYOWfw4JjBbpLYgXDDDP8yJ4094P1M_ez4Dc,4586
3
+ rubibot/types.py,sha256=i1Pvzr1Jy9JOpxgLFc64V6grZMOZ00r0abU-QE8RLeo,6767
4
+ pyrubikabotapi-0.0.1.dist-info/LICENCE,sha256=mds18XAwr8xSo4pIhwMfB9spNkvrm5KKmLl_KIKQky4,1095
5
+ pyrubikabotapi-0.0.1.dist-info/METADATA,sha256=nRj-WRGD_IMrtln4uadd50gJvRJMkgLqaloLEHrdEaw,1415
6
+ pyrubikabotapi-0.0.1.dist-info/WHEEL,sha256=WnJ8fYhv8N4SYVK2lLYNI6N0kVATA7b0piVUNvqIIJE,91
7
+ pyrubikabotapi-0.0.1.dist-info/top_level.txt,sha256=TAhaSYH8WLX-dab28vY3_Wif-mrUw1pmS_RJOFaiogE,8
8
+ pyrubikabotapi-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.3.3)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ rubibot
rubibot/__init__.py ADDED
@@ -0,0 +1,837 @@
1
+ import json
2
+ import os
3
+ import time
4
+ import requests
5
+ from typing import Any, List, Optional
6
+ from rubibot import helper
7
+ from rubibot import types
8
+
9
+ # In the name of Allah
10
+ # Developer: Alireza Sadeghian
11
+ # URL: https://github.com/alireza-sadeghian/PyRubikaBotAPI
12
+ # Channel: https://rubika.ir/pyrubikabotapi
13
+ # Faghat Heydar Amiralmomenin ast
14
+
15
+ """
16
+ Welcome to PyRubikaBotAPI
17
+
18
+ """
19
+
20
+ class RubiBot:
21
+ """
22
+ This is a main class for RubiBot
23
+ Using this class, you can define handlers for your Rubika robots and respond appropriately to updates using methods.
24
+
25
+ for example:
26
+
27
+ import rubibot
28
+ bot = rubibot.RubiBot('TOKEN') # You need to get this token from @BotFather
29
+ # Now you can define handlers for your bot and use methods.
30
+
31
+ See more examples on our Rubika channel.
32
+ https://rubika.ir/PyRubikaBotAPI
33
+
34
+ توضیحات فارسی:
35
+ این اصلی ترین کلاس برای ربات شماست
36
+ برای استفاده از امکانات کتابخانه یک شیء از این کلاس ایجاد کنید
37
+
38
+
39
+ """
40
+
41
+ def __init__(self, token):
42
+ self.TOKEN = token
43
+ self.BASE_URL = f"https://botapi.rubika.ir/v3/{self.TOKEN}"
44
+ self._message_handlers = []
45
+ self.OFFSET_FILE = "noi.json" # noi: n → next, o → offset, i → id
46
+
47
+ def message_handler(
48
+ self,
49
+ commands: Optional[List[str]]=None,
50
+ content_types: Optional[List[str]]=None
51
+ ):
52
+ """
53
+ It is responsible for managing messages received from the user.
54
+ It handles all types of messages such as text, video, Voice, Poll, etc.
55
+ As a parameter to the decorator function, it passes :class:`rubibot.types.Message` object.
56
+ All message handlers are stored and executed in the order in which they are written.
57
+
58
+ for example:
59
+
60
+ bot = RubiBot('token')
61
+
62
+ # Handle /start command
63
+ @bot.message_handler(commands=['start'])
64
+ def start(message: rubibot.types.Message):
65
+ bot.send_message(message.chat_id, "Hello from RubiBot!")
66
+
67
+ # Handle all sticker messages
68
+ @bot.message_handler(content_types=['sticker'])
69
+ def sticker_handler(message):
70
+ bot.send_file(message.chat.id, message.sticker.file.id, "This is your sticker image")
71
+
72
+ # Handle all sent messages of text type
73
+ @bot.message_handler()
74
+ def handler(message):
75
+ bot.reply_to(chat_id=message, text=message.text)
76
+
77
+ :param commands: list of commands
78
+
79
+ :param content_types: Supported message content types ↓
80
+ ['text', 'file', 'location', 'sticker', 'contact', 'poll']
81
+
82
+ :return: decorated function
83
+
84
+ توضیحات فارسی:
85
+ این تابع پیام های دریافتی و بروزرسانی هارا مدیریت می کند
86
+ برای استفاده از این تابع یک دکوریتور از آن ایجاد کنید و تابع مورد نظر
87
+ برای هندل کردن پیام هارا به عنوان تابع ورودی برای دکوریتور تعریف کنید
88
+
89
+ این دکوریتور یک شیء از کلاس مسیج
90
+ از پیام دریافتی توسط کاربر را به عنوان ورودی به تابعی که مشخص کرده اید می دهد
91
+
92
+ هندلر های پیام به ترتیب افزوده شدن اجرا می شوند
93
+ و بیش از یک هندلر برای یک پیام اجرا نمی شود!
94
+
95
+ پارامتر ها:
96
+ commands: لیست دستوراتی که میخواهید هندلر شما فقط آنها را هندل کند
97
+ content_types: فیلتر کردن نوع محتوا برای هندل شدن در هندلر شما
98
+
99
+ جهت دیدن مثال های بیشتر و دریافت سورس کد های
100
+ آماده و آزمایشی به کانال ما در سوپراپلیکیشن روبیکا سر بزنید
101
+ https://rubika.ir/PyRubikaBotAPI
102
+ """
103
+
104
+
105
+ LISTOFCONTENTTYPES = ['text', 'file', 'location', 'sticker', 'contact', 'poll']
106
+
107
+ if isinstance(commands, str):
108
+ commands = [commands]
109
+
110
+ if content_types is None:
111
+ content_types = ['text']
112
+
113
+ if isinstance(content_types, str):
114
+ content_types = [content_types]
115
+
116
+ for ct in content_types:
117
+ if ct not in LISTOFCONTENTTYPES:
118
+ raise ValueError(f"{ct} is not supported, supported values: {LISTOFCONTENTTYPES}")
119
+
120
+ def decorator(handler):
121
+ self._message_handlers.append({
122
+ "handler": handler,
123
+ "filters": {
124
+ "commands": commands,
125
+ "content_types": content_types
126
+ }
127
+ })
128
+ return handler
129
+
130
+
131
+ return decorator
132
+
133
+
134
+ def get_updates(self,offset=None, limit=10):
135
+ """
136
+ for get updates from rubika
137
+
138
+ :param offset: If you want to receive only new updates when receiving updates, put the value `next_offset_id` from the previous request here. If you leave nothing, by default all previous updates will be sent in this request.
139
+
140
+ :param limit: Limit on number of updates, Default: 10
141
+
142
+ :return: Tuple of(updates, next_ofsset_id) # for polling
143
+
144
+ """
145
+ if limit > 100 or limit < 1:
146
+ raise ValueError("The limit cannot be greater than 100 and less than 1.")
147
+
148
+ params = {"limit": limit}
149
+ if offset:
150
+ params["offset_id"] = offset
151
+ try:
152
+ res = requests.post(f"{self.BASE_URL}/getUpdates", json=params, timeout=15)
153
+ except Exception as e:
154
+ raise Exception("Error: {}".format(e))
155
+ data = res.json()
156
+ if not res or res.status_code !=200:
157
+ raise Exception("Error in receiving updates: {}".format(data.get("status")))
158
+
159
+ updates = []
160
+ for upd in data.get("data", {}).get("updates"):
161
+ updates.append(types.Update(json.dumps(upd)))
162
+ return updates, data.get("data").get("next_offset_id")
163
+
164
+
165
+ def __load_offset(self):
166
+ if not os.path.exists(self.OFFSET_FILE):
167
+ self.__save_offset(None)
168
+ return None
169
+
170
+ with open(self.OFFSET_FILE, "r", encoding="utf-8") as f:
171
+ return json.load(f).get("next_offset_id")
172
+
173
+ def __save_offset(self, offset_id):
174
+ with open(self.OFFSET_FILE, "w", encoding="utf-8") as f:
175
+ json.dump(
176
+ {"next_offset_id": offset_id},
177
+ f,
178
+ ensure_ascii=False,
179
+ indent=4
180
+ )
181
+
182
+ def polling(self, t: int = 2, limit=10):
183
+ """
184
+ This function receives updates from Rubica indefinitely and repeatedly at specified intervals and manages them using written handlers.
185
+
186
+ **Warning: This method is only for testing the bot in local mode and should not be used as the main method for receiving and handling updates. This method is not optimal and if you do this, a large number of requests will be sent to Rubika and you will be limited.
187
+
188
+ :param t: The time interval between requests
189
+
190
+ :param limit: Limit the number of updates received per request
191
+
192
+ :return:
193
+
194
+ توضیحات فارسی:
195
+ در ابتدا دقت کنید که از این تابع فقط و فقط به صورت آزمایشی و برای
196
+ تست کردن ربات خود استفاده کنید و به هیچ عنوان برای دریافت آپدیت ها
197
+ به صورت دائم از این متود استفاده نکنید و به جای آن
198
+ از وبهوک استفاده کنید
199
+
200
+ این متد را در آخر سورس کد خود فراخوانی کنید تا بروزرسانی ها را در بازه
201
+ های زمانی مشخص از روبیکا دریافت کند و توسط هندلر های ایجاد شده توسط شما هندل کند
202
+ """
203
+ next_offset_id = self.__load_offset()
204
+
205
+ while True:
206
+ updates, next_offset_id = self.get_updates(offset=next_offset_id, limit=limit)
207
+ if not next_offset_id:
208
+ next_offset_id = self.__load_offset()
209
+ self.__save_offset(next_offset_id)
210
+ self.process_new_updates(updates)
211
+ time.sleep(t)
212
+
213
+ def send_message(
214
+ self,chat_id,text,
215
+ chat_keypad=None,
216
+ inline_keypad=None,
217
+ disable_notification=False,
218
+ _reply_to_message_id = None
219
+ ):
220
+
221
+ """
222
+ Use this method to send text messages.
223
+
224
+ **Warning: Do not send more than about 4096 characters each message
225
+
226
+ :param chat_id: The unique identifier of a chat, such as a private conversation or a group/Channel
227
+ :type chat_id: :obj:`str`
228
+
229
+ :param text: Text to send
230
+ :type text: :obj:`str`
231
+
232
+ :param chat_keypad: Buttons at the bottom of the page that are sent with the message
233
+ :type chat_keypad: :obj:`rubibot.types.ChatKeypad` or :obj:`rubibot.types.ChatKeypadRemove`
234
+
235
+ :param inline_keypad: In-text buttons that appear below the message text
236
+ :type inline_keypad: :obj:`rubibot.types.InlineKeypad`
237
+
238
+ :param disable_notification: Send a message with silent notification
239
+ :type disable_notification: :obj:`bool`
240
+
241
+ :param _reply_to_message_id: It is used to reply to messages,
242
+ but you should use :method:`rubibot.RubiBot.reply_to()` to reply to plain text.
243
+
244
+ :return: On success, the send message id is returned
245
+ :rtype: :obj:`str`
246
+
247
+ توضیحات فارسی:
248
+ از این متود برای ارسال پیام متنی به یک چت مشخص استفاده کنید
249
+ طول پیام شما نباید بیشتر از 4096 کاراکتر باشد و گرنه خطا دریافت می کنید
250
+
251
+ پارامتر ها:
252
+
253
+ chat_id: شناسه چت مورد نظر برای ارسال پیام متنی
254
+ text: پیام مورد نظر
255
+ chat_keypad: اگر میخواهید همراه با پیام، دکمه هایی در پایین صفحه برای کاربر ارسال شود
256
+ ابتدا کی پد خود را با استفاده از:
257
+ `rubibot.types.ChatKeypad`
258
+ ایجاد کنید و آن را در اینجا قرار دهید
259
+ inline_keypad: اگر میخواهید دکمه هایی در زیر پیام ارسالی ایجاد شود ابتدا کی پد خود را با استفاده از:
260
+ `rubibot.types.InlineKeypad`
261
+ ایجاد کنید و سپس آن را اینجا قرار دهید
262
+ disable_notification: اگر میخواهید پیام شما با اعلان بدون صدا ارسال شود این مقدار را برابر
263
+ `True` قرار دهید
264
+ """
265
+
266
+ data = {"chat_id": chat_id, "text": text}
267
+
268
+ if chat_keypad:
269
+ type_ = chat_keypad.type
270
+ data["chat_keypad_type"] = type_
271
+ if type_ != "Remove":
272
+ data["chat_keypad"] = chat_keypad._get_data()
273
+
274
+ if inline_keypad:
275
+ data["inline_keypad"] = inline_keypad._get_data()
276
+
277
+ if disable_notification:
278
+ data["disable_notification"] = True
279
+
280
+ if _reply_to_message_id:
281
+ data["reply_to_message_id"] = _reply_to_message_id
282
+
283
+ method_name = 'sendMessage'
284
+ return helper._make_request(self.TOKEN, method_name, data)
285
+
286
+
287
+
288
+ def reply_to(
289
+ self, message: types.Message,
290
+ text, chat_keypad=None,
291
+ inline_keypad=None,
292
+ disable_notification=False
293
+ ):
294
+ """
295
+ Send a text message as a reply
296
+ This function is for your convenience in sending text messages as replies, but to send other types of messages as replies, you must use the `reply_to_message_id` value available in those methods.
297
+
298
+ :param message: message to reply
299
+ :type message: :obj:`rubibot.types.Message`
300
+
301
+ The rest of the values ​​are the same as those defined in `rubibot.RubiBot.send_message()`.
302
+
303
+ :return: On success, the send message id is returned
304
+ :rtype: :obj:`str`
305
+
306
+ توضیحات فارسی:
307
+ برای ارسال پیام های متنی و ریپلای کردن آن ها از این متود استفاده کنید
308
+ """
309
+
310
+
311
+ return self.send_message(
312
+ message.chat_id, text,
313
+ chat_keypad, inline_keypad,
314
+ disable_notification,
315
+ message.message_id
316
+ )
317
+
318
+
319
+ def get_me(self):
320
+ """
321
+ It does not receive any input and returns information about the bot.
322
+
323
+ :return: Bot information
324
+ :rtype: :obj:`rubibot.types.Bot`
325
+ """
326
+ res = requests.post("{}/getMe".format(self.BASE_URL))
327
+ r = res.json().get("data")
328
+ bot = r.get("bot")
329
+ bot_id = bot.get("bot_id")
330
+ bot_title = bot.get("bot_title")
331
+ avatar = bot.get("avatar", {})
332
+ avatar_id = avatar.get("file_id")
333
+ avatar_name = avatar.get("file_name")
334
+ avatar_size = avatar.get("size")
335
+ avatar_ = types.File(avatar_id, avatar_name, avatar_size)
336
+ description = bot.get("description")
337
+ username = bot.get("user_name")
338
+ start_msg = bot.get("start_message")
339
+ share_url = bot.get("share_url")
340
+
341
+ return types.Bot(bot_id, bot_title, avatar_, description, username, start_msg, share_url)
342
+
343
+ def send_poll(self, chat_id: str, poll: types.ChatPoll):
344
+ """
345
+ Send a poll to a specific chat
346
+
347
+ :param chat_id: Desired chat ID
348
+ :type chat_id: :obj:`str`
349
+
350
+ :param poll: Desired poll
351
+ :type poll: :obj:`rubibot.types.ChatPoll`
352
+
353
+ :return: On success, the send message id is returned
354
+ :rtype: :obj:`str`
355
+ """
356
+ data = poll._get_data()
357
+ data["chat_id"] = chat_id
358
+ method_name = 'sendPoll'
359
+ return helper._make_request(self.TOKEN, method_name, data)
360
+
361
+
362
+ def send_contact(
363
+ self, chat_id: str, first_name: str, last_name: str,
364
+ phone_number: str, chat_keypad: types.ChatKeypad= None,
365
+ inline_keypad: types.InlineKeypad = None, disable_notification: bool = False,
366
+ reply_to_message_id: str = None
367
+ ):
368
+ """
369
+ Send a contact to a specific chat
370
+
371
+ :param chat_id: Desired Chat ID
372
+ :type chat_id: :obj:`str`
373
+
374
+ :param first_name: Contact's first name
375
+ :type first_name: :obj:`str`
376
+
377
+ :param last_name: Contact's last name
378
+ :type last_name: :obj:`str`
379
+
380
+ :param phone_number: Contact's phone number
381
+ :type phone_number: :obj:`str`
382
+
383
+ The rest of the values ​​are the same as those defined in `rubibot.RubiBot.send_message()`.
384
+
385
+ :return: On success, the send message id is returned
386
+ :rtype: :obj:`str`
387
+
388
+ """
389
+ data = {
390
+ "chat_id": chat_id,
391
+ "first_name": first_name,
392
+ "last_name": last_name,
393
+ "phone_number": phone_number
394
+ }
395
+
396
+ if chat_keypad:
397
+ type_ = chat_keypad.type
398
+ data["chat_keypad_type"] = type_
399
+ data["chat_keypad"] = chat_keypad._get_data()
400
+
401
+ if inline_keypad:
402
+ data["inline_keypad"] = inline_keypad._get_data()
403
+
404
+ if disable_notification:
405
+ data["disable_notification"] = True
406
+
407
+ if reply_to_message_id:
408
+ data["reply_to_message_id"] = reply_to_message_id
409
+
410
+ method_name = 'sendContact'
411
+ return helper._make_request(self.TOKEN, method_name, data)
412
+
413
+ def get_chat(self, chat_id: str):
414
+ """
415
+ Get the desired chat information, including name and username.
416
+
417
+ :param chat_id: Desired Chat ID
418
+ :type chat_id: :obj:`str`
419
+
420
+ :return: Chat information
421
+ :rtype: :obj:`rubibot.types.Chat`
422
+
423
+ """
424
+ data = {
425
+ "chat_id": chat_id
426
+ }
427
+
428
+ res = requests.post("{}/getChat".format(self.BASE_URL), json=data)
429
+ data = res.json().get("data")
430
+ if data:
431
+ chat = data.get("chat")
432
+ chat_id = chat.get("chat_id")
433
+ chat_type = chat.get("chat_type")
434
+ user_id = chat.get("user_id")
435
+ first_name = chat.get("first_name")
436
+ last_name = chat.get("last_name")
437
+ title = chat.get("title")
438
+ username = chat.get("username")
439
+ return types.Chat(chat_id, chat_type, user_id, first_name, last_name, title, username)
440
+ else:
441
+ raise Exception("Rubika Error: {}".format(res.json().get("status")))
442
+
443
+
444
+
445
+ def forward(self, from_chat_id: str, to_chat_id: str, message_id: str, disable_notification: bool = False):
446
+ """
447
+ forward a message
448
+
449
+ """
450
+ data = {
451
+ "from_chat_id": from_chat_id,
452
+ "message_id": message_id,
453
+ "to_chat_id": to_chat_id,
454
+ "disable_notification": disable_notification
455
+ }
456
+
457
+ method_name = 'forwardMessage'
458
+ return helper._make_request(self.TOKEN, method_name, data)
459
+
460
+ def edit_message(
461
+ self, chat_id: str, message_id: str,
462
+ new_text: str, new_inline_keypad: types.InlineKeypad = None
463
+ ):
464
+ """
465
+ Edit a message
466
+
467
+ توضیحات فارسی:
468
+
469
+ پارامتر های قابل توضیح:
470
+ new_text: متنی که میخواهید جایگزین متن قبلی شود
471
+ new_inline_keypad: اگر میخواهید دکمه های درون متنی خود را نیز تغییر بدهید، مقدار جدیدی برای این ورودی قرار دهید
472
+ """
473
+
474
+ data = {
475
+ "chat_id": chat_id,
476
+ "message_id": message_id,
477
+ "text": new_text
478
+ }
479
+
480
+ method_name = 'editMessageText'
481
+ helper._make_request(self.TOKEN, method_name, data)
482
+
483
+ if new_inline_keypad:
484
+ data.pop("text")
485
+ data["inline_keypad"] = new_inline_keypad._get_data()
486
+
487
+ method_name = 'editMessageKeypad'
488
+ return helper._make_request(self.TOKEN, method_name, data)
489
+
490
+ return True
491
+
492
+ def delete_message(self, chat_id: str, message_id: str):
493
+ """
494
+ Delete a message
495
+
496
+ """
497
+ data = {
498
+ "chat_id": chat_id,
499
+ "message_id": message_id
500
+ }
501
+
502
+ method_name = 'deleteMessage'
503
+ return helper._make_request(self.TOKEN, method_name, data)
504
+
505
+ def set_commands(self, *args):
506
+ """
507
+ Setting commands for the bot, this way any new user who logs into your bot will see the commands you register here in the user interface and can use them.
508
+
509
+ :param args: Desired commands
510
+ :type args: :obj:`rubibot.types.BotCommand`
511
+
512
+ :return: On success, return True
513
+ :rtype: :obj:`bool`
514
+
515
+ """
516
+ data = {
517
+ "bot_commands": []
518
+ }
519
+ for arg in args:
520
+ data["bot_commands"].append(arg._get_data())
521
+
522
+ method_name = 'setCommands'
523
+ return helper._make_request(self.TOKEN, method_name, data)
524
+
525
+
526
+ def edit_chat_keypad(self, chat_id: str, chat_keypad):
527
+ """
528
+ Edit ChatKeypad
529
+
530
+ """
531
+ data = {"chat_id": chat_id}
532
+ type_ = chat_keypad._get_type()
533
+ data["chat_keypad_type"] = type_
534
+ if type_ != "Remove":
535
+ data["chat_keypad"] = chat_keypad._get_data()
536
+
537
+ method_name = 'editChatKeypad'
538
+ return helper._make_request(self.TOKEN, method_name, data)
539
+
540
+ def set_webhook(self, url, type="ReceiveUpdate"):
541
+ data = {"url": url, "type": type}
542
+ method_name = 'updateBotEndpoints'
543
+ return helper._make_request(self.TOKEN, method_name, data)
544
+
545
+
546
+ def get_file(self, file_id):
547
+ """
548
+ Get the download address of a file by file ID
549
+
550
+ for example:
551
+
552
+ download_url = bot.get_file('file_id')
553
+ file = bot.download_file(download_url)
554
+ # Now the file sent from the user has been downloaded and stored in the file variable.
555
+
556
+ :param file_id: The file id
557
+ :type file_id: :obj:`str`
558
+
559
+ :return: file download url
560
+ :rtype: :obj:`str`
561
+
562
+ توضیحات فارسی:
563
+ آیدی فایل را به عنوان ورودی قرار دهید تا لینک دانلود به شما بازگردانده شود
564
+ با لینک دانلود میتوانید فایل را دانلود کنید
565
+
566
+ """
567
+ data ={
568
+ "file_id": file_id
569
+ }
570
+
571
+ method_name = 'getFile'
572
+ return helper._make_request(self.TOKEN, method_name, data)
573
+
574
+ def download_file(self, file_url) -> bytes:
575
+ """
576
+ Download a file by download URL
577
+
578
+ :param file_url: The file url
579
+ :type file_url: :obj:`str`
580
+
581
+ :return: The File
582
+ :rtype: :obj:`bytes`
583
+
584
+ توضیحات فارسی:
585
+ لینک دانلود را به عنوان ورودی قرار دهید تا فایل مورد نظر به عنوان
586
+ خروجی به شما تحویل داده شود
587
+
588
+ """
589
+ res = requests.get(file_url)
590
+ if res.status_code != 200:
591
+ raise Exception(f"Download File Error")
592
+ return res.content
593
+
594
+ def request_send_file(self, file_type):
595
+ """
596
+ Request to upload a file to Rubika servers
597
+
598
+ توضیحات فارسی:
599
+ درخواست برای آپلود یک فایل روی سرور های روبیکا
600
+ * به صورت پیشفرض نیازی به استفاده از این متود ندارید
601
+ و برای ارسال فایل به یک چت می توانید از متود های زیر استفاده کنید:
602
+ `rubibot.RubiBot.send_photo`
603
+ `rubibot.RubiBot.send_file`
604
+ و دیگر متود های ارسال فایل
605
+
606
+ """
607
+ if file_type not in ['File', 'Image', 'Voice', 'Video', 'Music', 'Gif']:
608
+ raise ValueError("Sorry")
609
+ data = {"type": file_type}
610
+ method_name = 'requestSendFile'
611
+ return helper._make_request(self.TOKEN, method_name, data)
612
+
613
+
614
+ def __send_file(
615
+ self, file_type, chat_id, file, text, chat_keypad: types.ChatKeypad, inline_keypad: types.InlineKeypad, reply_to_message_id, disable_notification
616
+ ):
617
+
618
+ data = {"chat_id": chat_id, "text": text}
619
+
620
+ if chat_keypad:
621
+ data["chat_keypad_type"] = chat_keypad._get_type()
622
+ data["chat_keypad"] = chat_keypad._get_data()
623
+ if inline_keypad:
624
+ data["inline_keypad"] = inline_keypad._get_data()
625
+ if reply_to_message_id:
626
+ data["reply_to_message_id"] = reply_to_message_id
627
+ if disable_notification:
628
+ data["disable_notification"] = disable_notification
629
+
630
+ upload_url = self.request_send_file(file_type)
631
+
632
+ data["file_id"] = self.upload_file(file, upload_url)
633
+
634
+ method_name = 'sendFile'
635
+ return helper._make_request(self.TOKEN, method_name, data)
636
+
637
+ def _create_file_type(self, file):
638
+ if hasattr(file, "read"):
639
+ return file
640
+ elif isinstance(file, str):
641
+ return open(file, 'rb')
642
+ else:
643
+ raise ValueError('invalid type')
644
+
645
+ def upload_file(self, file, upload_url):
646
+ file = self._create_file_type(file)
647
+ files = {"file": ("file", file)}
648
+ res = requests.post(
649
+ upload_url,
650
+ files=files
651
+ )
652
+ file.close()
653
+
654
+ return helper._chek_result_request(res)
655
+
656
+ def send_photo(
657
+ self, chat_id: str, photo: Any,
658
+ text: str, chat_keypad: Optional[types.ChatKeypad]=None,
659
+ inline_keypad: Optional[types.InlineKeypad]=None,
660
+ reply_to_message_id:Optional[str]=None, disable_notification: bool=False
661
+ ):
662
+ """
663
+ Send an image with a maximum size of 10 MB.
664
+ Supported formats: PNG, JPG, GIF, WEBP
665
+
666
+ :param photo: the photo to send
667
+ :type photo: :obj:`bytes` or File Path in Disk
668
+
669
+ The rest of the values ​​are the same as those defined in `rubibot.RubiBot.send_message()`.
670
+
671
+ :return: On success, the send message id is returned
672
+ :rtype: :obj:`str`
673
+ """
674
+
675
+ return self.__send_file(
676
+ "Image", chat_id, photo, text, chat_keypad, inline_keypad,
677
+ reply_to_message_id, disable_notification
678
+ )
679
+
680
+ def send_voice(
681
+ self, chat_id: str, voice: Any,
682
+ text: str, chat_keypad: Optional[types.ChatKeypad]=None,
683
+ inline_keypad: Optional[types.InlineKeypad]=None,
684
+ reply_to_message_id:Optional[str]=None, disable_notification: bool=False
685
+ ):
686
+ """
687
+ Send a Voice.
688
+ Supported formats: MP3
689
+
690
+ :param voice: the voice to send
691
+ :type voice: :obj:`bytes` or File Path in Disk
692
+
693
+ The rest of the values ​​are the same as those defined in `rubibot.RubiBot.send_message()`.
694
+
695
+ :return: On success, the send message id is returned
696
+ :rtype: :obj:`str`
697
+ """
698
+
699
+ return self.__send_file(
700
+ "Voice", chat_id, voice, text, chat_keypad, inline_keypad,
701
+ reply_to_message_id, disable_notification
702
+ )
703
+
704
+ def send_video(
705
+ self, chat_id: str, video: Any,
706
+ text: str, chat_keypad: Optional[types.ChatKeypad]=None,
707
+ inline_keypad: Optional[types.InlineKeypad]=None,
708
+ reply_to_message_id:Optional[str]=None, disable_notification: bool=False
709
+ ):
710
+ """
711
+ Send a Video with a maximum size of 50 MB.
712
+ Supported formats: MP4
713
+
714
+ :param video: the video to send
715
+ :type video: :obj:`bytes` or File Path in Disk
716
+
717
+ The rest of the values ​​are the same as those defined in `rubibot.RubiBot.send_message()`.
718
+
719
+ :return: On success, the send message id is returned
720
+ :rtype: :obj:`str`
721
+ """
722
+
723
+ return self.__send_file(
724
+ "Video", chat_id, video, text, chat_keypad, inline_keypad,
725
+ reply_to_message_id, disable_notification
726
+ )
727
+
728
+ def send_gif(
729
+ self, chat_id: str, gif: Any,
730
+ text: str, chat_keypad: Optional[types.ChatKeypad]=None,
731
+ inline_keypad: Optional[types.InlineKeypad]=None,
732
+ reply_to_message_id:Optional[str]=None, disable_notification: bool=False
733
+ ):
734
+ """
735
+ Send a Gif that must be without sound.
736
+ Supported formats: MP4
737
+
738
+ :param gif: the gif to send
739
+ :type gif: :obj:`bytes` or File Path in Disk
740
+
741
+ The rest of the values ​​are the same as those defined in `rubibot.RubiBot.send_message()`.
742
+
743
+ :return: On success, the send message id is returned
744
+ :rtype: :obj:`str`
745
+ """
746
+
747
+ return self.__send_file(
748
+ "Gif", chat_id, gif, text, chat_keypad, inline_keypad,
749
+ reply_to_message_id, disable_notification
750
+ )
751
+
752
+ def send_file(
753
+ self, chat_id: str, file: Any,
754
+ text: str, chat_keypad: Optional[types.ChatKeypad]=None,
755
+ inline_keypad: Optional[types.InlineKeypad]=None,
756
+ reply_to_message_id:Optional[str]=None, disable_notification: bool=False
757
+ ):
758
+ """
759
+ Send an other files with a maximum size of 50MB.
760
+ Supported formats: all
761
+
762
+ :param file: the file to send
763
+ :type file: :obj:`bytes` or File Path in Disk
764
+
765
+ The rest of the values ​​are the same as those defined in `rubibot.RubiBot.send_message()`.
766
+
767
+ :return: On success, the send message id is returned
768
+ :rtype: :obj:`str`
769
+
770
+ """
771
+
772
+ return self.__send_file(
773
+ "File", chat_id, file, text, chat_keypad, inline_keypad,
774
+ reply_to_message_id, disable_notification
775
+ )
776
+
777
+
778
+ def _test_message_handler(self, handler, message):
779
+ filters = handler["filters"]
780
+ if filters["commands"]:
781
+ if not message.text:
782
+ return False
783
+ cmd = message.text.split()[0][1:]
784
+ if cmd not in filters["commands"]:
785
+ return False
786
+
787
+ if filters["content_types"]:
788
+ if message._get_content_type() not in filters["content_types"]:
789
+ return False
790
+
791
+ return True
792
+
793
+
794
+ def process_new_updates(self, updates: list[types.Update]):
795
+ """
796
+ Processing new updates
797
+ Usage instructions:
798
+ First convert the received updates into an object of the `rubibot.types.Update` class and pass this object as input to this function
799
+
800
+ Warning: The input to this function must be in the form of a list.
801
+
802
+ for exapmle:
803
+
804
+ update = rubibot.types.Update(myupdate) # Updates received from Webhook
805
+ bot.process_new_updates([update])
806
+ # Now the received update is processed by the handlers you defined. This is one of the most important steps in running your bot.
807
+
808
+ see more example:
809
+ https://github.com/alireza-sadeghian/PyRubikaBotAPI
810
+
811
+ :param updates: Received updates
812
+ :type updates: :obj:`list` of :obj:`rubibot.types.Updates`
813
+
814
+ توضیحات فارسی:
815
+ اگر از پولینگ استفاده می کنید نیازی به این ندارید
816
+ اگر از وبهوک استفاده میکنید، ابتدا جیسون دریافتی را به شیء آپدیت
817
+ از کتابخانه روبی بات تبدیل کنید و سپس آن را به صورت لیست به عنوان ورودی این تابع قرار دهید
818
+ تا با استفاده از هندلر هایی که ثبت کرده اید هندل شود
819
+ """
820
+ if not isinstance(updates, List):
821
+ raise Exception("Invalid type for updates")
822
+
823
+ for upd in updates:
824
+
825
+ if not isinstance(upd, types.Update):
826
+ raise Exception("Invalid type for update")
827
+
828
+ message = helper._to_message(upd.dict)
829
+ for handler in self._message_handlers:
830
+ if self._test_message_handler(handler, message):
831
+ handler["handler"](message)
832
+ break
833
+
834
+
835
+
836
+ # soon...
837
+ # This is not the end of our work... 😎
rubibot/helper.py ADDED
@@ -0,0 +1,109 @@
1
+ import requests
2
+ from rubibot.types import AuxData, Contact, File, ForwardedFrom, Location, Message, Poll, PollStatus, Sticker
3
+ def _to_message(raw) -> Message:
4
+
5
+ type_ = raw.get("type")
6
+ chat_id = raw.get("chat_id")
7
+ # removed_message_id = raw.get("removed_message_id") # In the new update. soon...
8
+ new_message = raw.get("new_message")
9
+ # updated_message = raw.get("updated_message") # In the new update. soon...
10
+ message_id = new_message.get("message_id")
11
+ text = new_message.get("text")
12
+ time = new_message.get("time")
13
+ is_edited : bool = new_message.get("is_edited")
14
+ sender_type = new_message.get("sender_type") # User or Bot
15
+ sender_id = new_message.get("sender_id")
16
+ aux_data = new_message.get("aux_data", {})
17
+ aux = AuxData(aux_data.get("start_id"), aux_data.get("button_id"))
18
+ reply_to_message_id = new_message.get("reply_to_message_id")
19
+ forwarded_from_data = new_message.get("forwarded_from", {})
20
+ type_forward = forwarded_from_data.get("type_from")
21
+ forward_message_id = forwarded_from_data.get("message_id")
22
+ forward_chat_id = forwarded_from_data.get("from_chat_id")
23
+ forward_sender_id = forwarded_from_data.get("from_sender_id")
24
+ forwarded_from = ForwardedFrom(type_forward, forward_message_id, forward_chat_id, forward_sender_id)
25
+ file_data = new_message.get("file", {})
26
+ file_id = file_data.get("file_id")
27
+ file_name = file_data.get("file_name")
28
+ file_size = file_data.get("size")
29
+ file = File(file_id, file_name, file_size)
30
+ location_data = new_message.get("location", {})
31
+ longitude = location_data.get("longitude")
32
+ latitude = location_data.get("latitude")
33
+ location = Location(longitude, latitude)
34
+ sticker_data = new_message.get("sticker", {})
35
+ sticker_id = sticker_data.get("sticker_id")
36
+ sticker_file_data = sticker_data.get("file", {})
37
+ sticker_file_id = sticker_file_data.get("file_id")
38
+ sticker_file_name = sticker_file_data.get("file_name")
39
+ sticker_file_size = sticker_file_data.get("size")
40
+ sticker_file = File(sticker_file_id, sticker_file_name, sticker_file_size)
41
+ sticker_emoji_character = sticker_data.get("emoji_character")
42
+ sticker = Sticker(sticker_id, sticker_file, sticker_emoji_character)
43
+ contact_data = new_message.get("contact_message", {})
44
+ contact_phone_number = contact_data.get("phone_number")
45
+ contact_first_name = contact_data.get("first_name")
46
+ contact_last_name = contact_data.get("last_name")
47
+ contact = Contact(contact_first_name, contact_last_name, contact_phone_number)
48
+ poll_data = new_message.get("poll", {})
49
+ poll_question = poll_data.get("question")
50
+ poll_options = poll_data.get("options")
51
+ poll_status_data = poll_data.get("poll_status", {})
52
+ poll_status_state = poll_status_data.get("state")
53
+ poll_status_selection_index = poll_status_data.get("selection_index")
54
+ poll_status_percent_vote_options = poll_status_data.get("percent_vote_options")
55
+ poll_status_total_vote = poll_status_data.get("total_vote")
56
+ poll_status_show_total_votes: bool = poll_status_data.get("show_total_votes")
57
+ poll_status = PollStatus(
58
+ poll_status_state,
59
+ poll_status_selection_index,
60
+ poll_status_percent_vote_options,
61
+ poll_status_total_vote,
62
+ poll_status_show_total_votes
63
+ )
64
+ poll = Poll(poll_question, poll_options, poll_status)
65
+
66
+
67
+
68
+ message = Message(
69
+ type_,
70
+ chat_id,
71
+ message_id,
72
+ text,
73
+ time,
74
+ is_edited,
75
+ sender_type,
76
+ sender_id,
77
+ aux,
78
+ reply_to_message_id,
79
+ forwarded_from,
80
+ file,
81
+ location,
82
+ sticker,
83
+ contact,
84
+ poll
85
+ )
86
+
87
+ return message
88
+
89
+
90
+ def _chek_result_request(res):
91
+ res = res.json()
92
+ if res["status"] != "OK":
93
+ error = res.get('dev_message', res.get('status'))
94
+ raise Exception("Rubika Error: {}".format(error))
95
+ data = res.get("data", {})
96
+ if data.get('message_id'):
97
+ return data.get('message_id')
98
+ if data.get('file_id'):
99
+ return data.get('file_id')
100
+ if data.get('upload_url'):
101
+ return data.get('upload_url')
102
+ if data.get('download_url'):
103
+ return data.get('download_url')
104
+ return None
105
+
106
+
107
+ def _make_request(token, method_name, data=None):
108
+ result = requests.post("https://botapi.rubika.ir/v3/{0}/{1}".format(token, method_name), json=data)
109
+ return _chek_result_request(result)
rubibot/types.py ADDED
@@ -0,0 +1,242 @@
1
+ import json
2
+
3
+
4
+ class Update:
5
+ def __init__(self, update):
6
+ self._update = json.loads(update)
7
+ if self._update.get("update"):
8
+ self.dict = self._update.get("update")
9
+ else:
10
+ self.dict = self._update
11
+
12
+
13
+ class AuxData:
14
+ def __init__(self, start_id, button_id):
15
+ self.start_id = start_id
16
+ self.button_id = button_id
17
+
18
+
19
+ class File:
20
+
21
+ def __init__(self, file_id, file_name, size):
22
+ self.id = file_id
23
+ self.name = file_name
24
+ self.size = size
25
+
26
+ class Chat:
27
+ def __init__(self, chat_id, chat_type, user_id, first_name, last_name, title, username):
28
+ self.id = chat_id
29
+ self.type = chat_type
30
+ self.user_id = user_id
31
+ self.first_name = first_name
32
+ self.last_name = last_name
33
+ self.title = title
34
+ self.username = username
35
+
36
+
37
+ class ForwardedFrom:
38
+ def __init__(self, type_, message_id, chat_id, sender_id):
39
+ self.type = type_
40
+ self.message_id = message_id
41
+ self.chat_id = chat_id
42
+ self.sender_id = sender_id
43
+
44
+
45
+
46
+ class Location:
47
+ def __init__(self, longitude, latitude):
48
+ self.longitude = longitude
49
+ self.latitude = latitude
50
+
51
+
52
+ class Sticker:
53
+ def __init__(self, sticker_id: str, file: File, emoji_character: str):
54
+ self.id = sticker_id
55
+ self.file = file
56
+ self.emoji_character= emoji_character
57
+
58
+ class Contact:
59
+ def __init__(self, first_name, last_name, phone_number):
60
+ self.first_name = first_name
61
+ self.last_name = last_name
62
+ self.phone_number = phone_number
63
+
64
+ class PollStatus:
65
+ def __init__(self, state, selection_index, percent_vote_options, total_vote, show_total_votes):
66
+ self.state = state
67
+ self.selection_index = selection_index
68
+ self.percent_vote_options = percent_vote_options
69
+ self.total_vote = total_vote
70
+ self.show_total_votes = show_total_votes
71
+
72
+ class Poll:
73
+ def __init__(self, question, options, status: PollStatus):
74
+ self.question = question
75
+ self.options = options
76
+ self.status = status
77
+
78
+
79
+ class Bot:
80
+
81
+ def __init__(self, bot_id, bot_title, bot_avatar: File, description , username, start_message, share_url):
82
+ self.id = bot_id
83
+ self.title = bot_title
84
+ self.avatar = bot_avatar
85
+ self.description = description
86
+ self.username = username
87
+ self.start_message = start_message
88
+ self.share_url = share_url
89
+
90
+
91
+ class BotCommand:
92
+ def __init__(self, command: str, description: str):
93
+ self.command = command
94
+ self.description = description
95
+ def _get_data(self):
96
+ return {
97
+ "command": self.command,
98
+ "description": self.description
99
+ }
100
+
101
+
102
+ class ChatPoll:
103
+ def __init__(self, question):
104
+ self.question = question
105
+ self.options = []
106
+
107
+ def add_options(self, *args: str):
108
+ if len(self.options) < 10:
109
+ if len(args) + len(self.options) > 10:
110
+ raise ValueError("You cannot add more than 10 options!")
111
+ for arg in args:
112
+ self.options.append(arg)
113
+
114
+ def _get_data(self):
115
+ return {
116
+ "question": self.question,
117
+ "options": self.options
118
+ }
119
+
120
+
121
+
122
+ class ChatKeypad:
123
+ """
124
+
125
+
126
+ """
127
+
128
+ def __init__(self, resize_keyboard=False, on_time_keyboard=False):
129
+ self.keypad_rows = {"rows": []}
130
+ self._data = {"resize_keyboard": resize_keyboard, "on_time_keyboard": on_time_keyboard}
131
+ self.type = "New"
132
+
133
+ def add(self, *keypad_rows):
134
+ for kr in keypad_rows:
135
+ self.keypad_rows["rows"].append(kr._get_data())
136
+
137
+ self._data["rows"] = self.keypad_rows.get("rows")
138
+ def _get_data(self):
139
+ return self._data
140
+
141
+ def _get_type(self):
142
+ return self.type
143
+
144
+
145
+ class ChatKeypadRemove:
146
+ def __init__(self):
147
+ self.type = "Remove"
148
+
149
+ def _get_type(self):
150
+ return self.type
151
+
152
+
153
+ class InlineKeypad:
154
+
155
+ def __init__(
156
+ self,
157
+ resize_keyboard = False,
158
+ on_time_keyboard = False
159
+ ): # ): (: /: |:
160
+
161
+
162
+ self._data = {"rows": [], "resize_keyboard": resize_keyboard, "on_time_keyboard": on_time_keyboard}
163
+
164
+ def add(self, *args):
165
+
166
+ if not self._data.get("rows"):
167
+ self._data["rows"] = []
168
+ for kr in args:
169
+ self._data["rows"].append(kr._get_data())
170
+
171
+ def _get_data(self):
172
+ return self._data
173
+
174
+ class KeypadRow:
175
+ def __init__(self):
176
+ self._data = {}
177
+
178
+
179
+ def add(self, *buttons):
180
+ self._data["buttons"] = []
181
+ for btn in buttons:
182
+ self._data["buttons"].append(btn._get_data())
183
+
184
+ def _get_data(self):
185
+ return self._data
186
+
187
+
188
+ class KeypadSimpleButton:
189
+ def __init__(self, text, id):
190
+
191
+
192
+ self.id = id
193
+ self.text = text
194
+ self.type = "Simple"
195
+ self._data = {}
196
+
197
+ def _set_data(self):
198
+ self._data["id"] = self.id
199
+ self._data["type"] = self.type
200
+ self._data["button_text"] = self.text
201
+
202
+ return self._data
203
+
204
+ def _get_data(self):
205
+ return self._set_data()
206
+
207
+
208
+ class Message:
209
+ def __init__(
210
+ self, type_: str, chat_id: str, message_id: str,
211
+ text: str, time: float, is_edited: bool, sender_type: str, sender_id: str,
212
+ aux: AuxData, reply_to_message_id: str, forwarded_from: ForwardedFrom,
213
+ file: File, location: Location, sticker: Sticker, contact: Contact, poll: Poll
214
+ ):
215
+
216
+ self.type = type_
217
+ self.chat_id = chat_id
218
+ self.message_id = message_id
219
+ self.text = text
220
+ self.time = time
221
+ self.is_edited = is_edited
222
+ self.sender_type = sender_type
223
+ self.sender_id = sender_id
224
+ self.aux = aux
225
+ self.reply_to_message_id = reply_to_message_id
226
+ self.forwarded_from = forwarded_from
227
+ self.file = file
228
+ self.location = location
229
+ self.sticker = sticker
230
+ self.contact = contact
231
+ self.poll = poll
232
+
233
+ def _get_content_type(self):
234
+ if self.file and self.file.id: return "file"
235
+ if self.location and self.location.latitude is not None: return "location"
236
+ if self.sticker and self.sticker.id: return "sticker"
237
+ if self.contact and self.contact.phone_number: return "contact"
238
+ if self.poll and self.poll.question: return "poll"
239
+ return "text"
240
+
241
+ def __repr__(self):
242
+ return f"<rubibot.types.Message chat={self.chat_id} text={self.text}>"