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.
- rubigram/__init__.py +15 -0
- rubigram/client.py +294 -0
- rubigram/enums/__init__.py +52 -0
- rubigram/enums/buttons/__init__.py +13 -0
- rubigram/enums/buttons/button_calendar_type.py +20 -0
- rubigram/enums/buttons/button_location_type.py +20 -0
- rubigram/enums/buttons/button_selection_get_type.py +20 -0
- rubigram/enums/buttons/button_selection_search_type.py +20 -0
- rubigram/enums/buttons/button_selection_type.py +22 -0
- rubigram/enums/buttons/button_textbox_type_keypad.py +20 -0
- rubigram/enums/buttons/button_textbox_type_line.py +20 -0
- rubigram/enums/buttons/button_type.py +58 -0
- rubigram/enums/chat_action_type.py +22 -0
- rubigram/enums/chat_keypad_type.py +20 -0
- rubigram/enums/chat_type.py +24 -0
- rubigram/enums/enum.py +6 -0
- rubigram/enums/file_type.py +28 -0
- rubigram/enums/forwarded_from_type.py +22 -0
- rubigram/enums/live_location_status.py +20 -0
- rubigram/enums/message_sender_type.py +20 -0
- rubigram/enums/metadata_type.py +18 -0
- rubigram/enums/parse_mode.py +20 -0
- rubigram/enums/payment_status_type.py +20 -0
- rubigram/enums/poll_status_type.py +20 -0
- rubigram/enums/update_endpoint_type.py +26 -0
- rubigram/enums/update_type.py +28 -0
- rubigram/errors.py +16 -0
- rubigram/filters.py +865 -0
- rubigram/http_session.py +96 -0
- rubigram/methods/__init__.py +26 -0
- rubigram/methods/chats/__init__.py +10 -0
- rubigram/methods/chats/get_chat.py +53 -0
- rubigram/methods/decorators/__init__.py +25 -0
- rubigram/methods/decorators/on_inline_message.py +70 -0
- rubigram/methods/decorators/on_message.py +62 -0
- rubigram/methods/decorators/on_remove_message.py +65 -0
- rubigram/methods/decorators/on_start.py +65 -0
- rubigram/methods/decorators/on_started_bot.py +70 -0
- rubigram/methods/decorators/on_stop.py +65 -0
- rubigram/methods/decorators/on_stopped_bot.py +70 -0
- rubigram/methods/decorators/on_update_message.py +70 -0
- rubigram/methods/files/__init__.py +32 -0
- rubigram/methods/files/download_file.py +118 -0
- rubigram/methods/files/get_file.py +41 -0
- rubigram/methods/files/get_file_name.py +41 -0
- rubigram/methods/files/request_send_file.py +49 -0
- rubigram/methods/files/send_file.py +133 -0
- rubigram/methods/files/send_gif.py +51 -0
- rubigram/methods/files/send_music.py +97 -0
- rubigram/methods/files/send_photo.py +95 -0
- rubigram/methods/files/send_video.py +96 -0
- rubigram/methods/files/send_voice.py +96 -0
- rubigram/methods/files/upload_file.py +114 -0
- rubigram/methods/messages/__init__.py +34 -0
- rubigram/methods/messages/delete_messages.py +84 -0
- rubigram/methods/messages/edit_chat_keypad.py +68 -0
- rubigram/methods/messages/edit_message.py +82 -0
- rubigram/methods/messages/edit_message_keypad.py +72 -0
- rubigram/methods/messages/edit_message_text.py +68 -0
- rubigram/methods/messages/forward_message.py +78 -0
- rubigram/methods/messages/remove_chat_keypad.py +46 -0
- rubigram/methods/messages/send_contact.py +114 -0
- rubigram/methods/messages/send_location.py +108 -0
- rubigram/methods/messages/send_message.py +115 -0
- rubigram/methods/messages/send_poll.py +104 -0
- rubigram/methods/messages/send_sticker.py +98 -0
- rubigram/methods/network/__init__.py +12 -0
- rubigram/methods/network/request.py +129 -0
- rubigram/methods/settings/__init__.py +16 -0
- rubigram/methods/settings/set_command.py +50 -0
- rubigram/methods/settings/setup_endpoints.py +48 -0
- rubigram/methods/settings/update_bot_endpoint.py +62 -0
- rubigram/methods/updates/__init__.py +14 -0
- rubigram/methods/updates/get_me.py +35 -0
- rubigram/methods/updates/get_update.py +62 -0
- rubigram/methods/utilities/__init__.py +14 -0
- rubigram/methods/utilities/dispatcher.py +66 -0
- rubigram/methods/utilities/run.py +118 -0
- rubigram/rubino/__init__.py +6 -0
- rubigram/rubino/client.py +374 -0
- rubigram/rubino/network.py +129 -0
- rubigram/server/__init__.py +6 -0
- rubigram/server/server.py +245 -0
- rubigram/state/__init__.py +7 -0
- rubigram/state/state.py +121 -0
- rubigram/state/storage.py +131 -0
- rubigram/types/__init__.py +66 -0
- rubigram/types/aux_data.py +28 -0
- rubigram/types/bot.py +51 -0
- rubigram/types/bot_command.py +26 -0
- rubigram/types/buttons/__init__.py +13 -0
- rubigram/types/buttons/button.py +59 -0
- rubigram/types/buttons/button_calendar.py +40 -0
- rubigram/types/buttons/button_location.py +40 -0
- rubigram/types/buttons/button_number_picker.py +34 -0
- rubigram/types/buttons/button_selection.py +48 -0
- rubigram/types/buttons/button_selection_item.py +32 -0
- rubigram/types/buttons/button_string_picker.py +29 -0
- rubigram/types/buttons/button_text_box.py +40 -0
- rubigram/types/chat.py +86 -0
- rubigram/types/config/__init__.py +6 -0
- rubigram/types/config/object.py +442 -0
- rubigram/types/contact_message.py +29 -0
- rubigram/types/file.py +78 -0
- rubigram/types/forwarded_from.py +39 -0
- rubigram/types/keypads/__init__.py +7 -0
- rubigram/types/keypads/keypad.py +31 -0
- rubigram/types/keypads/keypad_row.py +23 -0
- rubigram/types/live_location.py +44 -0
- rubigram/types/location.py +24 -0
- rubigram/types/messages/__init__.py +8 -0
- rubigram/types/messages/inline_message.py +78 -0
- rubigram/types/messages/message.py +117 -0
- rubigram/types/messages/update_message.py +341 -0
- rubigram/types/metadata/__init__.py +7 -0
- rubigram/types/metadata/metadata.py +43 -0
- rubigram/types/metadata/metadata_parts.py +42 -0
- rubigram/types/payment_status.py +30 -0
- rubigram/types/poll.py +32 -0
- rubigram/types/poll_status.py +40 -0
- rubigram/types/sticker.py +33 -0
- rubigram/types/updates/__init__.py +7 -0
- rubigram/types/updates/update.py +917 -0
- rubigram/types/updates/updates.py +56 -0
- rubigram/utils/__init__.py +14 -0
- rubigram/utils/auto_delete.py +93 -0
- rubigram/utils/parser.py +99 -0
- rubigramclient-1.7.17.dist-info/METADATA +215 -0
- rubigramclient-1.7.17.dist-info/RECORD +132 -0
- rubigramclient-1.7.17.dist-info/WHEEL +5 -0
- rubigramclient-1.7.17.dist-info/licenses/LICENSE +21 -0
- 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,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}
|