RubigramClient 1.7.17__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.
Files changed (132) hide show
  1. rubigram/__init__.py +15 -0
  2. rubigram/client.py +294 -0
  3. rubigram/enums/__init__.py +52 -0
  4. rubigram/enums/buttons/__init__.py +13 -0
  5. rubigram/enums/buttons/button_calendar_type.py +20 -0
  6. rubigram/enums/buttons/button_location_type.py +20 -0
  7. rubigram/enums/buttons/button_selection_get_type.py +20 -0
  8. rubigram/enums/buttons/button_selection_search_type.py +20 -0
  9. rubigram/enums/buttons/button_selection_type.py +22 -0
  10. rubigram/enums/buttons/button_textbox_type_keypad.py +20 -0
  11. rubigram/enums/buttons/button_textbox_type_line.py +20 -0
  12. rubigram/enums/buttons/button_type.py +58 -0
  13. rubigram/enums/chat_action_type.py +22 -0
  14. rubigram/enums/chat_keypad_type.py +20 -0
  15. rubigram/enums/chat_type.py +24 -0
  16. rubigram/enums/enum.py +6 -0
  17. rubigram/enums/file_type.py +28 -0
  18. rubigram/enums/forwarded_from_type.py +22 -0
  19. rubigram/enums/live_location_status.py +20 -0
  20. rubigram/enums/message_sender_type.py +20 -0
  21. rubigram/enums/metadata_type.py +18 -0
  22. rubigram/enums/parse_mode.py +20 -0
  23. rubigram/enums/payment_status_type.py +20 -0
  24. rubigram/enums/poll_status_type.py +20 -0
  25. rubigram/enums/update_endpoint_type.py +26 -0
  26. rubigram/enums/update_type.py +28 -0
  27. rubigram/errors.py +16 -0
  28. rubigram/filters.py +865 -0
  29. rubigram/http_session.py +96 -0
  30. rubigram/methods/__init__.py +26 -0
  31. rubigram/methods/chats/__init__.py +10 -0
  32. rubigram/methods/chats/get_chat.py +53 -0
  33. rubigram/methods/decorators/__init__.py +25 -0
  34. rubigram/methods/decorators/on_inline_message.py +70 -0
  35. rubigram/methods/decorators/on_message.py +62 -0
  36. rubigram/methods/decorators/on_remove_message.py +65 -0
  37. rubigram/methods/decorators/on_start.py +65 -0
  38. rubigram/methods/decorators/on_started_bot.py +70 -0
  39. rubigram/methods/decorators/on_stop.py +65 -0
  40. rubigram/methods/decorators/on_stopped_bot.py +70 -0
  41. rubigram/methods/decorators/on_update_message.py +70 -0
  42. rubigram/methods/files/__init__.py +32 -0
  43. rubigram/methods/files/download_file.py +118 -0
  44. rubigram/methods/files/get_file.py +41 -0
  45. rubigram/methods/files/get_file_name.py +41 -0
  46. rubigram/methods/files/request_send_file.py +49 -0
  47. rubigram/methods/files/send_file.py +133 -0
  48. rubigram/methods/files/send_gif.py +51 -0
  49. rubigram/methods/files/send_music.py +97 -0
  50. rubigram/methods/files/send_photo.py +95 -0
  51. rubigram/methods/files/send_video.py +96 -0
  52. rubigram/methods/files/send_voice.py +96 -0
  53. rubigram/methods/files/upload_file.py +114 -0
  54. rubigram/methods/messages/__init__.py +34 -0
  55. rubigram/methods/messages/delete_messages.py +84 -0
  56. rubigram/methods/messages/edit_chat_keypad.py +68 -0
  57. rubigram/methods/messages/edit_message.py +82 -0
  58. rubigram/methods/messages/edit_message_keypad.py +72 -0
  59. rubigram/methods/messages/edit_message_text.py +68 -0
  60. rubigram/methods/messages/forward_message.py +78 -0
  61. rubigram/methods/messages/remove_chat_keypad.py +46 -0
  62. rubigram/methods/messages/send_contact.py +114 -0
  63. rubigram/methods/messages/send_location.py +108 -0
  64. rubigram/methods/messages/send_message.py +115 -0
  65. rubigram/methods/messages/send_poll.py +104 -0
  66. rubigram/methods/messages/send_sticker.py +98 -0
  67. rubigram/methods/network/__init__.py +12 -0
  68. rubigram/methods/network/request.py +129 -0
  69. rubigram/methods/settings/__init__.py +16 -0
  70. rubigram/methods/settings/set_command.py +50 -0
  71. rubigram/methods/settings/setup_endpoints.py +48 -0
  72. rubigram/methods/settings/update_bot_endpoint.py +62 -0
  73. rubigram/methods/updates/__init__.py +14 -0
  74. rubigram/methods/updates/get_me.py +35 -0
  75. rubigram/methods/updates/get_update.py +62 -0
  76. rubigram/methods/utilities/__init__.py +14 -0
  77. rubigram/methods/utilities/dispatcher.py +66 -0
  78. rubigram/methods/utilities/run.py +118 -0
  79. rubigram/rubino/__init__.py +6 -0
  80. rubigram/rubino/client.py +374 -0
  81. rubigram/rubino/network.py +129 -0
  82. rubigram/server/__init__.py +6 -0
  83. rubigram/server/server.py +245 -0
  84. rubigram/state/__init__.py +7 -0
  85. rubigram/state/state.py +121 -0
  86. rubigram/state/storage.py +131 -0
  87. rubigram/types/__init__.py +66 -0
  88. rubigram/types/aux_data.py +28 -0
  89. rubigram/types/bot.py +51 -0
  90. rubigram/types/bot_command.py +26 -0
  91. rubigram/types/buttons/__init__.py +13 -0
  92. rubigram/types/buttons/button.py +59 -0
  93. rubigram/types/buttons/button_calendar.py +40 -0
  94. rubigram/types/buttons/button_location.py +40 -0
  95. rubigram/types/buttons/button_number_picker.py +34 -0
  96. rubigram/types/buttons/button_selection.py +48 -0
  97. rubigram/types/buttons/button_selection_item.py +32 -0
  98. rubigram/types/buttons/button_string_picker.py +29 -0
  99. rubigram/types/buttons/button_text_box.py +40 -0
  100. rubigram/types/chat.py +86 -0
  101. rubigram/types/config/__init__.py +6 -0
  102. rubigram/types/config/object.py +442 -0
  103. rubigram/types/contact_message.py +29 -0
  104. rubigram/types/file.py +78 -0
  105. rubigram/types/forwarded_from.py +39 -0
  106. rubigram/types/keypads/__init__.py +7 -0
  107. rubigram/types/keypads/keypad.py +31 -0
  108. rubigram/types/keypads/keypad_row.py +23 -0
  109. rubigram/types/live_location.py +44 -0
  110. rubigram/types/location.py +24 -0
  111. rubigram/types/messages/__init__.py +8 -0
  112. rubigram/types/messages/inline_message.py +78 -0
  113. rubigram/types/messages/message.py +117 -0
  114. rubigram/types/messages/update_message.py +341 -0
  115. rubigram/types/metadata/__init__.py +7 -0
  116. rubigram/types/metadata/metadata.py +43 -0
  117. rubigram/types/metadata/metadata_parts.py +42 -0
  118. rubigram/types/payment_status.py +30 -0
  119. rubigram/types/poll.py +32 -0
  120. rubigram/types/poll_status.py +40 -0
  121. rubigram/types/sticker.py +33 -0
  122. rubigram/types/updates/__init__.py +7 -0
  123. rubigram/types/updates/update.py +917 -0
  124. rubigram/types/updates/updates.py +56 -0
  125. rubigram/utils/__init__.py +14 -0
  126. rubigram/utils/auto_delete.py +93 -0
  127. rubigram/utils/parser.py +99 -0
  128. rubigramclient-1.7.17.dist-info/METADATA +215 -0
  129. rubigramclient-1.7.17.dist-info/RECORD +132 -0
  130. rubigramclient-1.7.17.dist-info/WHEEL +5 -0
  131. rubigramclient-1.7.17.dist-info/licenses/LICENSE +21 -0
  132. rubigramclient-1.7.17.dist-info/top_level.txt +1 -0
@@ -0,0 +1,66 @@
1
+ # RubigramClient - Rubika API library for python
2
+ # Copyright (C) 2025-present Javad <https://github.com/DevJavad>
3
+ # Github - https://github.com/DevJavad/rubigram
4
+
5
+
6
+ from typing import Union
7
+ import rubigram
8
+
9
+
10
+ class Dispatcher:
11
+ async def dispatcher(
12
+ self: "rubigram.Client",
13
+ update: Union["rubigram.types.Update", "rubigram.types.InlineMessage"]
14
+ ):
15
+ """
16
+ **Dispatch incoming updates to appropriate handlers.**
17
+ `await client.dispatcher(update)`
18
+
19
+ This method routes incoming updates to the correct handler functions
20
+ based on the update type. It supports both regular updates and
21
+ inline messages, and ensures that only one handler processes each update.
22
+
23
+ Args:
24
+ update (`Union[rubigram.types.Update, rubigram.types.InlineMessage]`):
25
+ The incoming update to dispatch to handlers.
26
+
27
+ Example:
28
+ .. code-block:: python
29
+
30
+ # This method is automatically called by the client
31
+ # when receiving updates from Rubigram
32
+
33
+ # Example update flow:
34
+ # 1. New message → message_handlers
35
+ # 2. Edited message → edit_message_handlers
36
+ # 3. Deleted message → delete_message_handlers
37
+ # 4. Bot start → start_bot_handlers
38
+ # 5. Bot stop → stop_bot_handlers
39
+ # 6. Inline message → inline_message_handlers
40
+
41
+ Note:
42
+ - Stops after the first handler that returns True (indicating a match)
43
+ - Each update type has its own dedicated handler list
44
+ - Inline messages are processed separately from regular updates
45
+ - The order of handler registration matters (first match wins)
46
+ """
47
+ if isinstance(update, rubigram.types.InlineMessage):
48
+ handlers = self.inline_message_handlers
49
+
50
+ else:
51
+ type = update.type
52
+ if type == "NewMessage":
53
+ handlers = self.new_message_handlers
54
+ elif type == "UpdatedMessage":
55
+ handlers = self.update_message_handlers
56
+ elif type == "RemovedMessage":
57
+ handlers = self.remove_message_handlers
58
+ elif type == "StartedBot":
59
+ handlers = self.started_bot_handlers
60
+ elif type == "StoppedBot":
61
+ handlers = self.stopped_bot_handlers
62
+
63
+ for handler in handlers:
64
+ matched = await handler(self, update)
65
+ if matched:
66
+ return
@@ -0,0 +1,118 @@
1
+ # RubigramClient - Rubika API library for python
2
+ # Copyright (C) 2025-present Javad <https://github.com/DevJavad>
3
+ # Github - https://github.com/DevJavad/rubigram
4
+
5
+
6
+ from datetime import datetime
7
+ import asyncio
8
+ import logging
9
+ import rubigram
10
+
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class Run:
16
+ """
17
+ Long-polling update receiver for Rubika Bot API.
18
+
19
+ This class provides methods for running the bot in long-polling mode,
20
+ continuously fetching updates from the API and dispatching them to
21
+ registered handlers. It includes timestamp validation to ensure only
22
+ recent updates are processed.
23
+
24
+ Note:
25
+ This class is typically used as a mixin or extension to the
26
+ Client class via monkey-patching or inheritance.
27
+
28
+ Example:
29
+ .. code-block:: python
30
+ # Monkey-patch the client with run methods
31
+ client = Client(token="YOUR_BOT_TOKEN")
32
+ client.receiver = Run.receiver.__get__(client, Client)
33
+ client.run = Run.run.__get__(client, Client)
34
+
35
+ # Run the bot in polling mode
36
+ client.run()
37
+ """
38
+
39
+ async def receiver(self: "rubigram.Client"):
40
+ """
41
+ Continuously receive and process updates via long-polling.
42
+
43
+ This method:
44
+ 1. Starts the client
45
+ 2. Enters an infinite loop to fetch updates
46
+ 3. Validates update timestamps to prevent replay attacks
47
+ 4. Dispatches valid updates to handlers
48
+ 5. Manages the update offset for incremental fetching
49
+ 6. Stops the client on error or exit
50
+
51
+ Workflow:
52
+ - Fetches up to 100 updates at a time
53
+ - Validates that message timestamps are recent (within 2 seconds)
54
+ - Sets the client on each update for handler access
55
+ - Updates the offset to avoid processing the same update twice
56
+ - Continues polling until an error occurs
57
+
58
+ Note:
59
+ Timestamp validation ensures updates are not older than 2 seconds
60
+ from current time to prevent processing delayed or replayed messages.
61
+
62
+ Raises:
63
+ Exception: Any error during update fetching or processing.
64
+
65
+ Example:
66
+ .. code-block:: python
67
+ # Manual receiver usage
68
+ await client.receiver()
69
+ """
70
+ await self.start()
71
+ try:
72
+ while True:
73
+ updates = await self.get_updates(100, self.offset_id)
74
+ if updates.updates:
75
+ for update in updates.updates:
76
+ time = update.update_time
77
+ now = int(datetime.now().timestamp())
78
+ if time and (time >= now or time + 2 >= now):
79
+ logger.debug("New Event, type: %s", update.type)
80
+ update.client = self
81
+ await self.dispatcher(update)
82
+
83
+ self.offset_id = updates.next_offset_id
84
+ finally:
85
+ await self.stop()
86
+
87
+ def run(self: "rubigram.Client"):
88
+ """
89
+ Run the bot in long-polling mode (blocking call).
90
+
91
+ This method:
92
+ 1. Creates and runs an asyncio event loop
93
+ 2. Executes the receiver method
94
+ 3. Handles KeyboardInterrupt for graceful shutdown
95
+ 4. Closes the event loop on exit
96
+
97
+ Note:
98
+ This is a blocking method that runs indefinitely until
99
+ interrupted by Ctrl+C or a fatal error.
100
+
101
+ Example:
102
+ .. code-block:: python
103
+ # Simple bot runner
104
+ client = Client(token="YOUR_BOT_TOKEN")
105
+ client.run = Run.run.__get__(client, Client)
106
+ client.run() # Blocks here
107
+ """
108
+ try:
109
+ logger.info(
110
+ "Run rubigram client, token: %s",
111
+ self.token[:20] + "*" * max(0, len(self.token) - 20)
112
+ )
113
+ asyncio.run(self.receiver())
114
+ except KeyboardInterrupt:
115
+ logger.info(
116
+ "Stop Runing rubigram client, token: %s",
117
+ self.token[:20] + "*" * max(0, len(self.token) - 20)
118
+ )
@@ -0,0 +1,6 @@
1
+ # RubigramClient - Rubika API library for python
2
+ # Copyright (C) 2025-present Javad <https://github.com/DevJavad>
3
+ # Github - https://github.com/DevJavad/rubigram
4
+
5
+
6
+ from .client import Rubino
@@ -0,0 +1,374 @@
1
+ # RubigramClient - Rubika API library for python
2
+ # Copyright (C) 2025-present Javad <https://github.com/DevJavad>
3
+ # Github - https://github.com/DevJavad/rubigram
4
+
5
+
6
+ from typing import Optional, Literal, Union
7
+ from .network import Network
8
+ from random import randint
9
+
10
+
11
+ class Rubino(Network):
12
+ def __init__(
13
+ self,
14
+ auth: str,
15
+ timeout: float = 30.0,
16
+ connect_timeout: float = 10.0,
17
+ read_timeout: float = 20.0,
18
+ max_connections: int = 100
19
+ ):
20
+ super().__init__(auth, timeout, connect_timeout, read_timeout, max_connections)
21
+
22
+ def rnd(self):
23
+ return randint(100000, 999999999)
24
+
25
+ async def app_post(
26
+ self,
27
+ file: str,
28
+ caption: Optional[str] = None,
29
+ post_type: Literal["Picture", "Video"] = "Picture",
30
+ profile_id: Optional[str] = None,
31
+ file_name: Optional[str] = None,
32
+ is_multi_file: Optional[str] = None,
33
+ ):
34
+ request = await self.request_upload(file, post_type, file_name, profile_id)
35
+ file_id, hash_file_receive = request["file_id"], request["hash_file_receive"]
36
+ data = {
37
+ "caption": caption,
38
+ "file_id": file_id,
39
+ "hash_file_receive": hash_file_receive,
40
+ "height": 800,
41
+ "width": 800,
42
+ "is_multi_file": is_multi_file,
43
+ "post_type": post_type,
44
+ "rnd": self.rnd(),
45
+ "thumbnail_file_id": file_id,
46
+ "thumbnail_hash_file_receive": hash_file_receive,
47
+ "profile_id": profile_id
48
+ }
49
+ return await self.request("addPost", data)
50
+
51
+ async def get_post_by_share_link(self, post_link: str):
52
+ return await self.request("getPostByShareLink", {"share_string": post_link.split("/")[-1]})
53
+
54
+ async def add_post_view_count(self, post_id: str, post_profile_id: str):
55
+ data = {"post_id": post_id, "post_profile_id": post_profile_id}
56
+ return await self.request("addPostViewCount", data)
57
+
58
+ async def add_view_story(self, story_profile_id: str, story_ids: Union[str, list[str]], profile_id: Optional[str] = None):
59
+ story_ids = story_ids if isinstance(story_ids, list) else [story_ids]
60
+ data = {
61
+ "story_profile_id": story_profile_id,
62
+ "story_ids": story_ids,
63
+ "profile_id": profile_id
64
+ }
65
+ return await self.request("addViewStory", data)
66
+
67
+ async def is_exist_username(self, username: str):
68
+ return await self.request("isExistUsername", {"username": username.replace("@", "")})
69
+
70
+ async def create_page(self, username: str, name: str, bio: Optional[str] = None):
71
+ return await self.request("createPage", {"username": username, "name": name, "bio": bio})
72
+
73
+ async def add_comment(self, content: str, post_id: str, post_profile_id: str, profile_id: Optional[str] = None):
74
+ data = {
75
+ "content": content,
76
+ "post_id": post_id,
77
+ "post_profile_id": post_profile_id,
78
+ "profile_id": profile_id,
79
+ "rnd": self.rnd()
80
+ }
81
+ return await self.request("addComment", data)
82
+
83
+ async def request_follow(self, followee_id: str, f_type: Literal["Follow", "Unfollow"] = "Follow", profile_id: Optional[str] = None):
84
+ data = {
85
+ "f_type": f_type,
86
+ "followee_id": followee_id,
87
+ "profile_id": profile_id
88
+ }
89
+ return self.request("requestFollow", data)
90
+
91
+ async def follow(self, followee_id: str, profile_id: Optional[str] = None):
92
+ return await self.request_follow(followee_id, "Follow", profile_id)
93
+
94
+ async def unfollow(self, followee_id: str, profile_id: Optional[str] = None):
95
+ return await self.request_follow(followee_id, "Unfollow", profile_id)
96
+
97
+ async def set_block_profile(self, block_id: str, action: Literal["Block", "Unblock"] = "Block", profile_id: Optional[str] = None):
98
+ data = {
99
+ "block_id": block_id,
100
+ "action": action,
101
+ "profile_id": profile_id
102
+ }
103
+ return self.request("setBlockProfile", data)
104
+
105
+ async def block_profile(self, block_id: str, profile_id: Optional[str] = None):
106
+ return await self.set_block_profile(block_id, "Block", profile_id)
107
+
108
+ async def unblock_profile(self, block_id: str, profile_id: Optional[str] = None):
109
+ return await self.set_block_profile(block_id, "Unblock", profile_id)
110
+
111
+ async def get_comments(self, post_id: str, post_profile_id: str, limit: Optional[int] = 100, profile_id: Optional[str] = None):
112
+ data = {
113
+ "post_id": post_id,
114
+ "post_profile_id": post_profile_id,
115
+ "limit": limit,
116
+ "profile_id": profile_id,
117
+ "equal": False,
118
+ "sort": "FromMax"
119
+ }
120
+ return await self.request("getComments", data)
121
+
122
+ async def get_my_profile_info(self, profile_id: Optional[str] = None):
123
+ return await self.request("getMyProfileInfo", {"profile_id": profile_id})
124
+
125
+ async def get_profile_list(self, limit: Optional[int] = 10):
126
+ data = {
127
+ "limit": limit,
128
+ "equal": False,
129
+ "sort": "FromMax"
130
+ }
131
+ return await self.request("getProfileList", data)
132
+
133
+ async def get_profile_stories(self, profile_id: Optional[str] = None, limit: Optional[int] = 10):
134
+ return self.request("getProfileStories", {"limit": limit, "profile_id": profile_id})
135
+
136
+ async def get_recent_following_posts(self, profile_id: Optional[str] = None, limit: Optional[int] = 10):
137
+ data = {
138
+ "limit": limit,
139
+ "equal": False,
140
+ "sort": "FromMax",
141
+ "profile_id": profile_id
142
+ }
143
+ return await self.request("getRecentFollowingPosts", data)
144
+
145
+ async def get_share_link(self, post_id: str, target_profile_id: str, profile_id: Optional[str] = None):
146
+ return await self.request("getShareLink", {"post_id": post_id, "target_profile_id": target_profile_id, "profile_id": profile_id})
147
+
148
+ async def get_story_id(self, post_profile_id: str, profile_id: Optional[str] = None):
149
+ return await self.request("getStoryIds", {"post_profile_id": post_profile_id, "profile_id": profile_id})
150
+
151
+ async def save_post(self, post_id: str, target_profile_id: str, profile_id: Optional[str] = None):
152
+ data = {
153
+ "action_type": "Bookmark",
154
+ "post_id": post_id,
155
+ "target_profile_id": target_profile_id,
156
+ "profile_id": profile_id
157
+ }
158
+ return await self.request("postBookmarkAction", data)
159
+
160
+ async def unsave_post(self, post_id: str, post_profile_id: str, profile_id: Optional[str] = None):
161
+ data = {
162
+ "action_type": "Unbookmark",
163
+ "post_id": post_id,
164
+ "post_profile_id": post_profile_id,
165
+ "profile_id": profile_id
166
+ }
167
+ return await self.request("postBookmarkAction", data)
168
+
169
+ async def update_profile(self, profile_id: Optional[str] = None):
170
+ data = {
171
+ "profile_id": profile_id,
172
+ "profile_status": "Public"
173
+ }
174
+ return await self.request("updateProfile", data)
175
+
176
+ async def like_post(self, post_id: str, target_profile_id: str, profile_id: Optional[str] = None):
177
+ data = {
178
+ "action_type": "Like",
179
+ "post_id": post_id,
180
+ "target_profile_id": target_profile_id,
181
+ "profile_id": profile_id
182
+ }
183
+ return await self.request("likePostAction", data)
184
+
185
+ async def unlike_post(self, post_id: str, target_profile_id: str, profile_id: Optional[str] = None):
186
+ data = {
187
+ "action_type": "Unlike",
188
+ "post_id": post_id,
189
+ "target_profile_id": target_profile_id,
190
+ "profile_id": profile_id
191
+ }
192
+ return await self.request("likePostAction", data)
193
+
194
+ async def like_comment(self, comment_id: str, post_id: str, profile_id: Optional[str] = None):
195
+ data = {
196
+ "action_type": "Like",
197
+ "comment_id": comment_id,
198
+ "post_id": post_id,
199
+ "profile_id": profile_id
200
+ }
201
+ return await self.request("likeCommentAction", data)
202
+
203
+ async def unlike_comment(self, comment_id: str, post_id: str, profile_id: Optional[str] = None):
204
+ data = {
205
+ "action_type": "Unlike",
206
+ "comment_id": comment_id,
207
+ "post_id": post_id,
208
+ "profile_id": profile_id
209
+ }
210
+ return await self.request("likeCommentAction", data)
211
+
212
+ async def get_saved_posts(self, limit: int = 10, profile_id: Optional[str] = None):
213
+ data = {
214
+ "equal": False,
215
+ "limit": limit,
216
+ "sort": "FromMax",
217
+ "profile_id": profile_id
218
+ }
219
+ return await self.request("getBookmarkedPosts", data)
220
+
221
+ async def get_archive_stories(self, limit: int = 10, start_id: Optional[str] = None, profile_id: Optional[str] = None):
222
+ data = {
223
+ "equal": False,
224
+ "limit": limit,
225
+ "start_id": start_id,
226
+ "sort": "FromMax",
227
+ "profile_id": profile_id
228
+ }
229
+ return await self.request("getMyArchiveStories", data)
230
+
231
+ async def get_profile_highlights(self, target_profile_id: str, limit: int = 10, profile_id: Optional[str] = None):
232
+ data = {
233
+ "equal": False,
234
+ "limit": limit,
235
+ "sort": "FromMax",
236
+ "target_profile_id": target_profile_id,
237
+ "profile_id": profile_id
238
+ }
239
+ return await self.request("getProfileHighlights", data)
240
+
241
+ async def get_blocked_profiles(self, limit: int = 50, max_id: Optional[str] = None, profile_id: Optional[str] = None):
242
+ data = {
243
+ "equal": False,
244
+ "limit": limit,
245
+ "max_id": max_id,
246
+ "sort": "FromMax",
247
+ "profile_id": profile_id
248
+ }
249
+ return await self.request("getBlockedProfiles", data)
250
+
251
+ async def get_profile_following(self, target_profile_id: str, limit: int = 50, profile_id: Optional[str] = None):
252
+ data = {
253
+ "equal": False,
254
+ "f_type": "Following",
255
+ "limit": limit,
256
+ "sort": "FromMax",
257
+ "target_profile_id": target_profile_id,
258
+ "profile_id": profile_id
259
+ }
260
+ return await self.request("getProfileFollowers", data)
261
+
262
+ async def get_profile_followers(self, target_profile_id: str, limit: int = 50, profile_id: Optional[str] = None):
263
+ data = {
264
+ "equal": False,
265
+ "f_type": "Follower",
266
+ "limit": limit,
267
+ "sort": "FromMax",
268
+ "target_profile_id": target_profile_id,
269
+ "profile_id": profile_id
270
+ }
271
+ return await self.request("getProfileFollowers", data)
272
+
273
+ async def get_my_stories_list(self, limit: Optional[int] = None, profile_id: Optional[str] = None):
274
+ data = {
275
+ "limit": limit,
276
+ "profile_id": profile_id
277
+ }
278
+ return await self.request("getMyStoriesList", data)
279
+
280
+ async def delete_story(self, story_id: str, profile_id: Optional[str] = None):
281
+ data = {
282
+ "profile_id": profile_id,
283
+ "story_id": story_id
284
+ }
285
+ return await self.request("deleteStory", data)
286
+
287
+ async def get_explore_posts(self, topic_id: str, limit: int = 50, max_id: Optional[str] = None, profile_id: Optional[str] = None):
288
+ data = {
289
+ "equal": False,
290
+ "limit": limit,
291
+ "max_id": max_id,
292
+ "sort": "FromMax",
293
+ "topic_id": topic_id,
294
+ "profile_id": profile_id
295
+ }
296
+ return await self.request("getExplorePosts", data)
297
+
298
+ async def search_profile(self, username: str, limit: int = 50, profile_id: Optional[str] = None):
299
+ data = {
300
+ "equal": False,
301
+ "limit": limit,
302
+ "sort": "FromMax",
303
+ "username": username.replace("@", ""),
304
+ "profile_id": profile_id
305
+ }
306
+ return await self.request("searchProfile", data)
307
+
308
+ async def search_in_rubino(self, username: str, limit: int = 50, profile_id: Optional[str] = None):
309
+ data = {
310
+ "equal": False,
311
+ "limit": limit,
312
+ "sort": "FromMax",
313
+ "username": username.startswith("@"),
314
+ "profile_id": profile_id
315
+ }
316
+ return await self.request("searchProfile", data)
317
+
318
+ async def get_hashtag_trend(self, limit: int = 50, profile_id: Optional[str] = None):
319
+ data = {
320
+ "equal": False,
321
+ "limit": limit,
322
+ "sort": "FromMax",
323
+ "profile_id": profile_id
324
+ }
325
+ return await self.request("getHashTagTrend", data)
326
+
327
+ async def search_hashtag(self, content: str, limit: int = 50, profile_id: Optional[str] = None):
328
+ data = {
329
+ "content": content,
330
+ "equal": False,
331
+ "limit": limit,
332
+ "sort": "FromMax",
333
+ "profile_id": profile_id
334
+ }
335
+ return await self.request("searchHashTag", data)
336
+
337
+ async def get_posts_by_hashtag(self, hashtag: str, limit: int = 50, profile_id: Optional[str] = None):
338
+ data = {
339
+ "equal": False,
340
+ "hashtag": hashtag,
341
+ "limit": limit,
342
+ "profile_id": profile_id
343
+ }
344
+ return await self.request("getPostsByHashTag", data)
345
+
346
+ async def remove_page(self, profile_id: str, record_id: str):
347
+ data = {
348
+ "model": "Profile",
349
+ "record_id": record_id,
350
+ "profile_id": profile_id
351
+ }
352
+ return await self.request("removeRecord", data)
353
+
354
+ async def get_new_follow_requests(self, profile_id: Optional[str] = None, limit: Optional[int] = 20):
355
+ data = {
356
+ "profile_id": profile_id,
357
+ "limit": limit,
358
+ "sort": "FromMax"
359
+ }
360
+ return await self.request("getNewFollowRequests", data)
361
+
362
+ async def action_on_request(self, request_id: str, profile_id: Optional[str] = None, action: Literal["Accept", "Decline"] = "Accept"):
363
+ data = {
364
+ "action": action,
365
+ "request_id": request_id,
366
+ "profile_id": profile_id
367
+ }
368
+ return await self.request("actionOnRequest", data)
369
+
370
+ async def accept_request(self, request_id: str, profile_id: Optional[str] = None):
371
+ return await self.action_on_request(request_id, profile_id)
372
+
373
+ async def reject_request(self, request_id: str, profile_id: Optional[str] = None):
374
+ return await self.action_on_request(request_id, profile_id, "Decline")
@@ -0,0 +1,129 @@
1
+ # RubigramClient - Rubika API library for python
2
+ # Copyright (C) 2025-present Javad <https://github.com/DevJavad>
3
+ # Github - https://github.com/DevJavad/rubigram
4
+
5
+
6
+ from typing import Optional, Any
7
+ from aiohttp import FormData
8
+ from ..http_session import HttpSession
9
+ from urllib.parse import urlparse
10
+ from random import randint
11
+ from pathlib import Path
12
+ from json import loads
13
+ import os
14
+
15
+
16
+ class Network:
17
+ def __init__(
18
+ self,
19
+ auth: str,
20
+ timeout: float = 30.0,
21
+ connect_timeout: float = 10.0,
22
+ read_timeout: float = 20.0,
23
+ max_connections: int = 100
24
+ ):
25
+ self.auth = auth
26
+ self.http = HttpSession(timeout, connect_timeout, read_timeout, max_connections)
27
+ self.api = f"https://rubino{randint(1, 30)}.iranlms.ir"
28
+ self.client = {
29
+ "app_name": "Main",
30
+ "app_version": "3.0.2",
31
+ "lang_code": "fa",
32
+ "package": "app.rbmain.a",
33
+ "platform": "Android"
34
+ }
35
+
36
+ async def start(self):
37
+ await self.http.connect()
38
+
39
+ async def stop(self):
40
+ await self.http.disconnect()
41
+
42
+ async def __aenter__(self):
43
+ await self.start()
44
+ return self
45
+
46
+ async def __aexit__(self, *args):
47
+ await self.stop()
48
+
49
+ async def request(self, method: str, data: dict[str, Any]):
50
+ json = {
51
+ "api_version": "0",
52
+ "auth": self.auth,
53
+ "client": self.client,
54
+ "data": data,
55
+ "method": method
56
+ }
57
+ async with self.http.session.post(self.api, json=json) as response:
58
+ response.raise_for_status()
59
+ return await response.json()
60
+
61
+ async def get_bytes(self, url: str) -> bytes:
62
+ async with self.http.session.get(url) as response:
63
+ response.raise_for_status()
64
+ return await response.read()
65
+
66
+ async def get_name(self, url: str) -> str:
67
+ parser = urlparse(url)
68
+ return os.path.basename(parser.path)
69
+
70
+ async def request_upload_file(
71
+ self,
72
+ file_name: str,
73
+ file_size: str,
74
+ file_type: str,
75
+ profile_id: str,
76
+ ):
77
+ data = {
78
+ "file_name": file_name,
79
+ "file_size": file_size,
80
+ "file_type": file_type,
81
+ "profile_id": profile_id,
82
+ }
83
+ return await self.request("requestUploadFile", data)
84
+
85
+ async def request_upload(
86
+ self,
87
+ file: str,
88
+ file_type: str,
89
+ file_name: Optional[str] = None,
90
+ profile_id: Optional[str] = None,
91
+ ):
92
+ path = Path(file)
93
+ if path.is_file():
94
+ data = path.read_bytes()
95
+ file_name = file_name if file_name else path.name
96
+ file_size = path.stat().st_size
97
+
98
+ elif file.startswith("http"):
99
+ data = await self.get_bytes(file)
100
+ file_name = file_name if file_name else await self.get_name(file)
101
+ file_size = len(data)
102
+
103
+ else:
104
+ raise Exception(f"Can't find this file : {file}")
105
+
106
+ request = await self.request_upload_file(file_name, file_size, file_type, profile_id)
107
+ request: dict[str, str] = request["data"]
108
+
109
+ file_id: str = request["file_id"]
110
+ hash_file_request: str = request["hash_file_request"]
111
+ server_url: str = request["server_url"]
112
+
113
+ headers = {
114
+ "auth": self.auth,
115
+ "chunk-size": str(file_size),
116
+ "file-id": file_id,
117
+ "hash-file-request": hash_file_request,
118
+ "content-length": str(file_size),
119
+ "part-number": "1",
120
+ "total-part": "1"
121
+ }
122
+ form = FormData()
123
+ form.add_field(
124
+ "file", data, filename=file_name, content_type="application/octet-stream"
125
+ )
126
+ async with self.http.session.post(server_url, data=form, headers=headers) as response:
127
+ text = await response.text()
128
+ hash_file_receive = loads(text)["data"]["hash_file_receive"]
129
+ return {"file_id": file_id, "hash_file_receive": hash_file_receive}