RubigramClient 1.4.0__tar.gz → 1.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of RubigramClient might be problematic. Click here for more details.
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/PKG-INFO +1 -1
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/RubigramClient.egg-info/PKG-INFO +1 -1
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/pyproject.toml +3 -2
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/rubigram/__init__.py +2 -2
- rubigramclient-1.4.2/rubigram/client.py +146 -0
- rubigramclient-1.4.2/rubigram/filters.py +165 -0
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/rubigram/method.py +88 -66
- rubigramclient-1.4.2/rubigram/network.py +58 -0
- rubigramclient-1.4.2/rubigram/types.py +359 -0
- rubigramclient-1.4.0/rubigram/client.py +0 -90
- rubigramclient-1.4.0/rubigram/filters.py +0 -113
- rubigramclient-1.4.0/rubigram/network.py +0 -30
- rubigramclient-1.4.0/rubigram/types.py +0 -473
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/LICENSE +0 -0
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/README.md +0 -0
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/RubigramClient.egg-info/SOURCES.txt +0 -0
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/RubigramClient.egg-info/dependency_links.txt +0 -0
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/RubigramClient.egg-info/requires.txt +0 -0
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/RubigramClient.egg-info/top_level.txt +0 -0
- {rubigramclient-1.4.0 → rubigramclient-1.4.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: RubigramClient
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: A simple and flexible Python library for building advanced Rubika bots with powerful message handling, inline buttons, and custom filters.
|
|
5
5
|
Author-email: Javad RZ <Javad.Py1385@gmail.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: RubigramClient
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: A simple and flexible Python library for building advanced Rubika bots with powerful message handling, inline buttons, and custom filters.
|
|
5
5
|
Author-email: Javad RZ <Javad.Py1385@gmail.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "RubigramClient"
|
|
3
|
-
version = "1.4.
|
|
3
|
+
version = "1.4.2"
|
|
4
4
|
description = "A simple and flexible Python library for building advanced Rubika bots with powerful message handling, inline buttons, and custom filters."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.7"
|
|
@@ -15,5 +15,6 @@ classifiers = [
|
|
|
15
15
|
]
|
|
16
16
|
dependencies = [
|
|
17
17
|
"aiohttp",
|
|
18
|
-
"aiofiles"
|
|
18
|
+
"aiofiles",
|
|
19
|
+
|
|
19
20
|
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional, Callable, Awaitable
|
|
3
|
+
from aiohttp import web
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from rubigram.types import Update, InlineMessage
|
|
6
|
+
from rubigram.method import Method
|
|
7
|
+
from rubigram.filters import Filter
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class Client(Method):
|
|
15
|
+
def __init__(self, token: str, endpoint: Optional[str] = None, host: str = "0.0.0.0", port: int = 8000):
|
|
16
|
+
self.token = token
|
|
17
|
+
self.endpoint = endpoint
|
|
18
|
+
self.host = host
|
|
19
|
+
self.port = port
|
|
20
|
+
self.offset_id = None
|
|
21
|
+
self.routes = web.RouteTableDef()
|
|
22
|
+
self.message_handlers = []
|
|
23
|
+
self.inline_handlers = []
|
|
24
|
+
super().__init__(token)
|
|
25
|
+
|
|
26
|
+
def on_message(self, *filters: Filter):
|
|
27
|
+
def decorator(func: Callable[[Client, Update], Awaitable]):
|
|
28
|
+
@wraps(func)
|
|
29
|
+
async def wrapper(client: Client, update: Update):
|
|
30
|
+
try:
|
|
31
|
+
combined_filter = filters[0] if filters else None
|
|
32
|
+
for filter in filters[1:]:
|
|
33
|
+
combined_filter = combined_filter & filter
|
|
34
|
+
|
|
35
|
+
if combined_filter is None or await combined_filter(update):
|
|
36
|
+
await func(client, update)
|
|
37
|
+
return True
|
|
38
|
+
return False
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.exception(f"Error in message handler {func.__name__}: {e}")
|
|
41
|
+
return False
|
|
42
|
+
self.message_handlers.append(wrapper)
|
|
43
|
+
return func
|
|
44
|
+
return decorator
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def on_inline_message(self, *filters: Filter):
|
|
48
|
+
def decorator(func: Callable[[Client, InlineMessage], Awaitable]):
|
|
49
|
+
@wraps(func)
|
|
50
|
+
async def wrapper(client: Client, update: InlineMessage):
|
|
51
|
+
try:
|
|
52
|
+
combined_filter = filters[0] if filters else None
|
|
53
|
+
for filter in filters[1:]:
|
|
54
|
+
combined_filter = combined_filter & filter
|
|
55
|
+
|
|
56
|
+
if combined_filter is None or await combined_filter(update):
|
|
57
|
+
await func(client, update)
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.exception(f"Error in inline handler {func.__name__}: {e}")
|
|
62
|
+
return False
|
|
63
|
+
self.inline_handlers.append(wrapper)
|
|
64
|
+
return func
|
|
65
|
+
return decorator
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def dispatch_update(self, update: Update):
|
|
69
|
+
for handler in self.message_handlers:
|
|
70
|
+
try:
|
|
71
|
+
matched = await handler(self, update)
|
|
72
|
+
if matched:
|
|
73
|
+
return
|
|
74
|
+
except Exception as error:
|
|
75
|
+
logger.exception(f"Error in handler dispatch: {error}")
|
|
76
|
+
|
|
77
|
+
async def dispatch_inline(self, update: InlineMessage):
|
|
78
|
+
for handler in self.inline_handlers:
|
|
79
|
+
try:
|
|
80
|
+
matched = await handler(self, update)
|
|
81
|
+
if matched:
|
|
82
|
+
return
|
|
83
|
+
except Exception as error:
|
|
84
|
+
logger.exception(f"Error in inline handler dispatch: {error}")
|
|
85
|
+
|
|
86
|
+
async def updater(self, data: dict):
|
|
87
|
+
if "inline_message" in data:
|
|
88
|
+
event = InlineMessage.from_dict(data["inline_message"])
|
|
89
|
+
await self.dispatch_inline(event)
|
|
90
|
+
elif "update" in data:
|
|
91
|
+
event = Update.from_dict(data["update"])
|
|
92
|
+
event.client = self
|
|
93
|
+
await self.dispatch_update(event)
|
|
94
|
+
|
|
95
|
+
async def set_endpoints(self):
|
|
96
|
+
if self.endpoint:
|
|
97
|
+
await self.update_bot_endpoint(f"{self.endpoint}/ReceiveUpdate", "ReceiveUpdate")
|
|
98
|
+
await self.update_bot_endpoint(f"{self.endpoint}/ReceiveInlineMessage", "ReceiveInlineMessage")
|
|
99
|
+
|
|
100
|
+
def run(self):
|
|
101
|
+
if self.endpoint:
|
|
102
|
+
@self.routes.post("/ReceiveUpdate")
|
|
103
|
+
async def receive_update(request: web.Request):
|
|
104
|
+
data = await request.json()
|
|
105
|
+
await self.updater(data)
|
|
106
|
+
return web.json_response({"status": "OK"})
|
|
107
|
+
|
|
108
|
+
@self.routes.post("/ReceiveInlineMessage")
|
|
109
|
+
async def receive_inline_message(request: web.Request):
|
|
110
|
+
data = await request.json()
|
|
111
|
+
await self.updater(data)
|
|
112
|
+
return web.json_response({"status": "OK"})
|
|
113
|
+
|
|
114
|
+
app = web.Application()
|
|
115
|
+
app.add_routes(self.routes)
|
|
116
|
+
|
|
117
|
+
async def on_startup(_):
|
|
118
|
+
await self.set_endpoints()
|
|
119
|
+
app.on_startup.append(on_startup)
|
|
120
|
+
web.run_app(app, host=self.host, port=self.port)
|
|
121
|
+
|
|
122
|
+
else:
|
|
123
|
+
async def polling():
|
|
124
|
+
while True:
|
|
125
|
+
try:
|
|
126
|
+
get_update = await self.get_update(100, self.offset_id)
|
|
127
|
+
if get_update.updates:
|
|
128
|
+
updates = get_update.updates
|
|
129
|
+
for update in updates:
|
|
130
|
+
if update.type == "NewMessage":
|
|
131
|
+
message_time = int(update.new_message.time)
|
|
132
|
+
elif update.type == "UpdatedMessage":
|
|
133
|
+
message_time = int(update.updated_message.time)
|
|
134
|
+
else:
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
now = int(datetime.now().timestamp())
|
|
138
|
+
if message_time >= now or message_time + 2 >= now:
|
|
139
|
+
await self.dispatch_update(update)
|
|
140
|
+
|
|
141
|
+
self.offset_id = get_update.next_offset_id
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.exception(f"Polling error: {e}")
|
|
144
|
+
await asyncio.sleep(1)
|
|
145
|
+
|
|
146
|
+
asyncio.run(polling())
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from rubigram.types import Update, InlineMessage
|
|
2
|
+
from typing import Union, Callable, Awaitable
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Filter:
|
|
7
|
+
def __init__(self, func: Callable[[Union[Update, InlineMessage]], bool | Awaitable[bool]]):
|
|
8
|
+
self.func = func
|
|
9
|
+
|
|
10
|
+
async def __call__(self, update: Union[Update, InlineMessage]) -> bool:
|
|
11
|
+
result = self.func(update)
|
|
12
|
+
if isinstance(result, Awaitable):
|
|
13
|
+
return await result
|
|
14
|
+
return result
|
|
15
|
+
|
|
16
|
+
def __and__(self, other: "Filter"):
|
|
17
|
+
async def combined(update):
|
|
18
|
+
return await self(update) and await other(update)
|
|
19
|
+
return Filter(combined)
|
|
20
|
+
|
|
21
|
+
def __or__(self, other: "Filter"):
|
|
22
|
+
async def combined(update):
|
|
23
|
+
return await self(update) or await other(update)
|
|
24
|
+
return Filter(combined)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class command(Filter):
|
|
28
|
+
def __init__(self, cmd: Union[str, list[str]], prefix: str = "/"):
|
|
29
|
+
self.cmd = cmd
|
|
30
|
+
self.prefix = prefix
|
|
31
|
+
super().__init__(self.filter)
|
|
32
|
+
|
|
33
|
+
def filter(self, update: Update):
|
|
34
|
+
commands = [self.cmd] if isinstance(self.cmd, str) else self.cmd
|
|
35
|
+
text = ""
|
|
36
|
+
if isinstance(update, Update):
|
|
37
|
+
if update.type == "NewMessage":
|
|
38
|
+
text = update.new_message.text
|
|
39
|
+
elif update.type == "UpdatedMessage":
|
|
40
|
+
text = update.updated_message.text
|
|
41
|
+
|
|
42
|
+
for cmd in commands:
|
|
43
|
+
if text.lower().startswith(self.prefix + cmd.lower()):
|
|
44
|
+
return True
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class regex(Filter):
|
|
49
|
+
def __init__(self, pattern: str):
|
|
50
|
+
self.pattern = pattern
|
|
51
|
+
super().__init__(self.filter)
|
|
52
|
+
|
|
53
|
+
def filter(self, update: Union[Update, InlineMessage]):
|
|
54
|
+
text = ""
|
|
55
|
+
if isinstance(update, Update):
|
|
56
|
+
if update.type == "NewMessage":
|
|
57
|
+
text = getattr(update.new_message, "text", "")
|
|
58
|
+
elif update.type == "UpdatedMessage":
|
|
59
|
+
text = getattr(update.updated_message, "text", "")
|
|
60
|
+
elif isinstance(update, InlineMessage):
|
|
61
|
+
text = getattr(update, "text", "")
|
|
62
|
+
|
|
63
|
+
if text:
|
|
64
|
+
return bool(re.search(self.pattern, text))
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class chat(Filter):
|
|
69
|
+
def __init__(self, chat_id: Union[str, list[str]]):
|
|
70
|
+
self.chat_id = chat_id
|
|
71
|
+
super().__init__(self.filter)
|
|
72
|
+
|
|
73
|
+
def filter(self, update: Union[Update, InlineMessage]):
|
|
74
|
+
chat_ids = [self.chat_id] if isinstance(
|
|
75
|
+
self.chat_id, str) else self.chat_id
|
|
76
|
+
return update.chat_id in chat_ids
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class button(Filter):
|
|
80
|
+
def __init__(self, button_id: Union[str, list[str]]):
|
|
81
|
+
self.button_id = button_id
|
|
82
|
+
super().__init__(self.filter)
|
|
83
|
+
|
|
84
|
+
def filter(self, update: InlineMessage):
|
|
85
|
+
if isinstance(update, InlineMessage):
|
|
86
|
+
button_ids = [self.button_id] if isinstance(
|
|
87
|
+
self.button_id, str) else self.button_id
|
|
88
|
+
return update.aux_data.button_id in button_ids
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def TEXT(update: Update):
|
|
93
|
+
return bool(update.new_message and getattr(update.new_message, "text", None))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def FILE(update: Update):
|
|
97
|
+
return bool(update.new_message and getattr(update.new_message, "file", None))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def LIVE(update: Update):
|
|
101
|
+
return bool(update.new_message and getattr(update.new_message, "live_location", None))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def POLL(update: Update):
|
|
105
|
+
return bool(update.new_message and getattr(update.new_message, "poll", None))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def CONTACT(update: Update):
|
|
109
|
+
return bool(update.new_message and getattr(update.new_message, "contact_message", None))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def STICKER(update: Update):
|
|
113
|
+
return bool(update.new_message and getattr(update.new_message, "sticker", None))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def LOCATION(update: Update):
|
|
117
|
+
return bool(update.new_message and getattr(update.new_message, "location", None))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def FORWARD(update: Update):
|
|
121
|
+
return bool(update.new_message and getattr(update.new_message, "forwarded_from", None))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def EDITED(update: Update):
|
|
125
|
+
if isinstance(update, Update) and update.type == "UpdatedMessage":
|
|
126
|
+
return update.updated_message.is_edited
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def PRIVATE(update: Update):
|
|
130
|
+
if isinstance(update, Update) and update.type == "NewMessage":
|
|
131
|
+
return update.new_message.sender_type in ["User", "Bot"]
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def FORWARD_BOT(update: Update):
|
|
136
|
+
if isinstance(update, Update) and update.type == "NewMessage" and update.new_message.forwarded_from:
|
|
137
|
+
return update.new_message.forwarded_from.type_from == "Bot"
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def FORWARD_USER(update: Update):
|
|
142
|
+
if isinstance(update, Update) and update.type == "NewMessage" and update.new_message.forwarded_from:
|
|
143
|
+
return update.new_message.forwarded_from.type_from == "User"
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def FORWARD_CHANNEL(update: Update):
|
|
148
|
+
if isinstance(update, Update) and update.type == "NewMessage" and update.new_message.forwarded_from:
|
|
149
|
+
return update.new_message.forwarded_from.type_from == "Channel"
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
text = Filter(TEXT)
|
|
154
|
+
file = Filter(FILE)
|
|
155
|
+
live = Filter(LIVE)
|
|
156
|
+
poll = Filter(POLL)
|
|
157
|
+
edited = Filter(EDITED)
|
|
158
|
+
contact = Filter(CONTACT)
|
|
159
|
+
sticker = Filter(STICKER)
|
|
160
|
+
location = Filter(LOCATION)
|
|
161
|
+
forward = Filter(FORWARD)
|
|
162
|
+
private = Filter(PRIVATE)
|
|
163
|
+
forward_bot = Filter(FORWARD_BOT)
|
|
164
|
+
forward_user = Filter(FORWARD_USER)
|
|
165
|
+
forward_channel = Filter(FORWARD_CHANNEL)
|
|
@@ -1,86 +1,79 @@
|
|
|
1
|
-
from
|
|
1
|
+
from .network import Network
|
|
2
2
|
from typing import Literal, Optional
|
|
3
|
-
from
|
|
3
|
+
from .types import Bot, Chat, Keypad, MessageId, Updates
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
class Method(Network):
|
|
6
7
|
def __init__(self, token: str):
|
|
7
8
|
super().__init__(token)
|
|
8
|
-
|
|
9
|
+
|
|
9
10
|
async def get_me(self) -> "Bot":
|
|
10
11
|
response = await self.request("getMe", {})
|
|
11
|
-
return Bot.
|
|
12
|
+
return Bot.from_dict(response["bot"])
|
|
12
13
|
|
|
13
14
|
async def get_chat(self, chat_id: str) -> "Chat":
|
|
14
15
|
response = await self.request("getChat", {"chat_id": chat_id})
|
|
15
|
-
return Chat.
|
|
16
|
-
|
|
17
|
-
async def get_update(self, limit: int = 1, offset_id: Optional[int] = None) ->
|
|
16
|
+
return Chat.from_dict(response["chat"])
|
|
17
|
+
|
|
18
|
+
async def get_update(self, limit: int = 1, offset_id: Optional[int] = None) -> "Updates":
|
|
18
19
|
response = await self.request("getUpdates", {"limit": limit, "offset_id": offset_id})
|
|
19
|
-
return
|
|
20
|
-
|
|
20
|
+
return Updates.from_dict(response)
|
|
21
|
+
|
|
21
22
|
async def get_file(self, file_id: str) -> str:
|
|
22
23
|
response = await self.request("getFile", {"file_id": file_id})
|
|
23
|
-
return response["
|
|
24
|
-
|
|
24
|
+
return response["download_url"]
|
|
25
|
+
|
|
25
26
|
async def set_command(self, command: list):
|
|
26
27
|
response = await self.request("setCommands", {"bot_commands": command})
|
|
27
28
|
return response
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
async def update_bot_endpoint(self, url: str, type: Literal["ReceiveUpdate", "ReceiveInlineMessage", "ReceiveQuery", "GetSelectionItem", "SearchSelectionItems"]):
|
|
30
31
|
response = await self.request("updateBotEndpoints", {"url": url, "type": type})
|
|
31
32
|
return response
|
|
32
|
-
|
|
33
|
-
async def request_send_file(self, type: str):
|
|
34
|
-
response = await self.request("requestSendFile", {"type": type})
|
|
35
|
-
return response["data"]["upload_url"]
|
|
36
|
-
|
|
37
|
-
async def upload_file(self, path: str, name: str, type: str):
|
|
38
|
-
upload_url = await self.request_send_file(type)
|
|
39
|
-
response = await self.request_upload_file(upload_url, path, name)
|
|
40
|
-
return response
|
|
41
|
-
|
|
42
|
-
async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> "MessageId":
|
|
43
|
-
data = {"from_chat_id": from_chat_id, "message_id": message_id, "to_chat_id": to_chat_id, "disable_notification": disable_notification}
|
|
44
|
-
response = await self.request("forwardMessage", data)
|
|
45
|
-
return MessageId.read(response["data"])
|
|
46
|
-
|
|
33
|
+
|
|
47
34
|
async def delete_message(self, chat_id: str, message_id: str):
|
|
48
35
|
await self.request("deleteMessage", {"chat_id": chat_id, "message_id": message_id})
|
|
49
|
-
|
|
36
|
+
|
|
50
37
|
async def remove_chat_keypad(self, chat_id: str):
|
|
51
38
|
await self.request("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "Remove"})
|
|
52
|
-
|
|
39
|
+
|
|
53
40
|
async def edit_chat_keypad(self, chat_id: str, chat_keypad):
|
|
54
41
|
await self.request("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "New", "chat_keypad": chat_keypad})
|
|
55
42
|
|
|
56
43
|
async def edit_message_keypad(self, chat_id: str, message_id: str, inline_keypad):
|
|
57
44
|
await self.request("editMessageKeypad", {"chat_id": chat_id, "message_id": message_id, "inline_keypad": inline_keypad})
|
|
58
|
-
|
|
45
|
+
|
|
59
46
|
async def edit_message_text(self, chat_id: str, message_id: str, text: str):
|
|
60
47
|
await self.request("editMessageText", {"chat_id": chat_id, "message_id": message_id, "text": text})
|
|
61
|
-
|
|
48
|
+
|
|
49
|
+
async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> "MessageId":
|
|
50
|
+
data = {"from_chat_id": from_chat_id, "message_id": message_id,
|
|
51
|
+
"to_chat_id": to_chat_id, "disable_notification": disable_notification}
|
|
52
|
+
response = await self.request("forwardMessage", data)
|
|
53
|
+
return MessageId.from_dict(response)
|
|
54
|
+
|
|
62
55
|
async def send_message(
|
|
63
56
|
self,
|
|
64
57
|
chat_id: str,
|
|
65
58
|
text: str,
|
|
66
59
|
chat_keypad: Keypad = None,
|
|
67
|
-
inline_keypad: Keypad= None,
|
|
60
|
+
inline_keypad: Keypad = None,
|
|
68
61
|
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
69
62
|
disable_notification: bool = None,
|
|
70
|
-
reply_to_message_id
|
|
63
|
+
reply_to_message_id=None
|
|
71
64
|
) -> "MessageId":
|
|
72
65
|
data = {
|
|
73
66
|
"chat_id": chat_id,
|
|
74
67
|
"text": text,
|
|
75
|
-
"chat_keypad": chat_keypad.
|
|
76
|
-
"inline_keypad": inline_keypad.
|
|
68
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
69
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
77
70
|
"chat_keypad_type": chat_keypad_type,
|
|
78
71
|
"disable_notification": disable_notification,
|
|
79
72
|
"reply_to_message_id": reply_to_message_id
|
|
80
73
|
}
|
|
81
74
|
response = await self.request("sendMessage", data)
|
|
82
|
-
return MessageId.
|
|
83
|
-
|
|
75
|
+
return MessageId.from_dict(response)
|
|
76
|
+
|
|
84
77
|
async def send_poll(
|
|
85
78
|
self,
|
|
86
79
|
chat_id: str,
|
|
@@ -96,15 +89,15 @@ class Method(NetWork):
|
|
|
96
89
|
"chat_id": chat_id,
|
|
97
90
|
"question": question,
|
|
98
91
|
"options": options,
|
|
99
|
-
"chat_keypad": chat_keypad.
|
|
100
|
-
"inline_keypad": inline_keypad.
|
|
92
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
93
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
101
94
|
"disable_notification": disable_notification,
|
|
102
95
|
"reply_to_message_id": reply_to_message_id,
|
|
103
96
|
"chat_keypad_type": chat_keypad_type
|
|
104
97
|
}
|
|
105
98
|
response = await self.request("sendPoll", data)
|
|
106
|
-
return MessageId.
|
|
107
|
-
|
|
99
|
+
return MessageId.from_dict(response)
|
|
100
|
+
|
|
108
101
|
async def send_location(
|
|
109
102
|
self,
|
|
110
103
|
chat_id: str,
|
|
@@ -120,15 +113,15 @@ class Method(NetWork):
|
|
|
120
113
|
"chat_id": chat_id,
|
|
121
114
|
"latitude": latitude,
|
|
122
115
|
"longitude": longitude,
|
|
123
|
-
"chat_keypad": chat_keypad.
|
|
124
|
-
"inline_keypad": inline_keypad.
|
|
116
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
117
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
125
118
|
"disable_notification": disable_notification,
|
|
126
119
|
"reply_to_message_id": reply_to_message_id,
|
|
127
120
|
"chat_keypad_type": chat_keypad_type
|
|
128
121
|
}
|
|
129
122
|
response = await self.request("sendLocation", data)
|
|
130
|
-
return MessageId.
|
|
131
|
-
|
|
123
|
+
return MessageId.from_dict(response)
|
|
124
|
+
|
|
132
125
|
async def send_contact(
|
|
133
126
|
self,
|
|
134
127
|
chat_id: str,
|
|
@@ -146,15 +139,15 @@ class Method(NetWork):
|
|
|
146
139
|
"first_name": first_name,
|
|
147
140
|
"last_name": last_name,
|
|
148
141
|
"phone_number": phone_number,
|
|
149
|
-
"chat_keypad": chat_keypad.
|
|
150
|
-
"inline_keypad": inline_keypad.
|
|
142
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
143
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
151
144
|
"disable_notification": disable_notification,
|
|
152
145
|
"reply_to_message_id": reply_to_message_id,
|
|
153
146
|
"chat_keypad_type": chat_keypad_type
|
|
154
147
|
}
|
|
155
148
|
response = await self.request("sendContact", data)
|
|
156
|
-
return MessageId.
|
|
157
|
-
|
|
149
|
+
return MessageId.from_dict(response)
|
|
150
|
+
|
|
158
151
|
async def send_sticker(
|
|
159
152
|
self,
|
|
160
153
|
chat_id: str,
|
|
@@ -168,41 +161,70 @@ class Method(NetWork):
|
|
|
168
161
|
data = {
|
|
169
162
|
"chat_id": chat_id,
|
|
170
163
|
"sticker_id": sticker_id,
|
|
171
|
-
"chat_keypad": chat_keypad.
|
|
172
|
-
"inline_keypad": inline_keypad.
|
|
164
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
165
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
173
166
|
"disable_notification": disable_notification,
|
|
174
167
|
"reply_to_message_id": reply_to_message_id,
|
|
175
168
|
"chat_keypad_type": chat_keypad_type
|
|
176
169
|
}
|
|
170
|
+
|
|
177
171
|
response = await self.request("sendSticker", data)
|
|
178
|
-
return MessageId.
|
|
179
|
-
|
|
172
|
+
return MessageId.from_dict(response)
|
|
173
|
+
|
|
174
|
+
async def request_send_file(self, type: str):
|
|
175
|
+
response = await self.request("requestSendFile", {"type": type})
|
|
176
|
+
return response["upload_url"]
|
|
177
|
+
|
|
178
|
+
async def upload_file(self, file: str, name: str, type: str):
|
|
179
|
+
upload_url = await self.request_send_file(type)
|
|
180
|
+
response = await self.request_upload_file(upload_url, file, name)
|
|
181
|
+
return response
|
|
182
|
+
|
|
183
|
+
async def download_file(self, file_id: str, file_name: str):
|
|
184
|
+
download_url = await self.get_file(file_id)
|
|
185
|
+
response = await self.request_download_file(download_url, file_name)
|
|
186
|
+
return response
|
|
187
|
+
|
|
180
188
|
async def send_file(
|
|
181
189
|
self,
|
|
182
190
|
chat_id: str,
|
|
183
|
-
|
|
191
|
+
file: str,
|
|
184
192
|
file_name: str,
|
|
185
|
-
type: Literal["File", "Image", "Voice",
|
|
193
|
+
type: Literal["File", "Image", "Voice",
|
|
194
|
+
"Music", "Gif", "Video"] = "File",
|
|
186
195
|
chat_keypad: Keypad = None,
|
|
187
196
|
inline_keypad: Keypad = None,
|
|
197
|
+
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
188
198
|
disable_notification: bool = False,
|
|
189
199
|
reply_to_message_id: str = None,
|
|
190
|
-
chat_keypad_type: Literal["New", "Remove"] = None,
|
|
191
200
|
) -> "MessageId":
|
|
192
|
-
file_id = await self.upload_file(
|
|
201
|
+
file_id = await self.upload_file(file, file_name, type)
|
|
193
202
|
data = {
|
|
194
203
|
"chat_id": chat_id,
|
|
195
204
|
"file_id": file_id,
|
|
196
|
-
"chat_keypad": chat_keypad.
|
|
197
|
-
"inline_keypad": inline_keypad.
|
|
205
|
+
"chat_keypad": chat_keypad.to_dict() if chat_keypad else None,
|
|
206
|
+
"inline_keypad": inline_keypad.to_dict() if inline_keypad else None,
|
|
198
207
|
"disable_notification": disable_notification,
|
|
199
208
|
"reply_to_message_id": reply_to_message_id,
|
|
200
209
|
"chat_keypad_type": chat_keypad_type,
|
|
201
210
|
}
|
|
202
211
|
response = await self.request("sendFile", data)
|
|
203
|
-
return MessageId.
|
|
204
|
-
|
|
205
|
-
async def
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
212
|
+
return MessageId.from_dict(response)
|
|
213
|
+
|
|
214
|
+
async def send_document(self, chat_id: str, document: str, name: str, **kwargs):
|
|
215
|
+
return await self.send_file(chat_id, document, name, "File", **kwargs)
|
|
216
|
+
|
|
217
|
+
async def send_photo(self, chat_id: str, photo: str, name: str, **kwargs):
|
|
218
|
+
return await self.send_file(chat_id, photo, name, "Image", **kwargs)
|
|
219
|
+
|
|
220
|
+
async def send_video(self, chat_id: str, video: str, name: str, **kwargs):
|
|
221
|
+
return await self.send_file(chat_id, video, name, "Video", **kwargs)
|
|
222
|
+
|
|
223
|
+
async def send_gif(self, chat_id: str, gif: str, name: str, **kwargs):
|
|
224
|
+
return await self.send_file(chat_id, gif, name, "Gif", **kwargs)
|
|
225
|
+
|
|
226
|
+
async def send_music(self, chat_id: str, music: str, name: str, **kwargs):
|
|
227
|
+
return await self.send_file(chat_id, music, name, "Music", **kwargs)
|
|
228
|
+
|
|
229
|
+
async def send_voice(self, chat_id: str, voice: str, name: str, **kwargs):
|
|
230
|
+
return await self.send_file(chat_id, voice, name, "Voice", **kwargs)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from aiohttp import ClientSession, FormData
|
|
2
|
+
from typing import Any, Optional, Dict, Union
|
|
3
|
+
import aiofiles
|
|
4
|
+
import re
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Network:
|
|
9
|
+
def __init__(self, token: str) -> None:
|
|
10
|
+
self.token: str = token
|
|
11
|
+
self.session: Optional[ClientSession] = None
|
|
12
|
+
self.api: str = f"https://botapi.rubika.ir/v3/{self.token}/"
|
|
13
|
+
|
|
14
|
+
async def get_session(self) -> ClientSession:
|
|
15
|
+
if self.session is None or self.session.closed:
|
|
16
|
+
self.session = ClientSession()
|
|
17
|
+
return self.session
|
|
18
|
+
|
|
19
|
+
async def close(self) -> None:
|
|
20
|
+
if self.session and not self.session.closed:
|
|
21
|
+
await self.session.close()
|
|
22
|
+
|
|
23
|
+
async def request(self, method: str, json: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
24
|
+
session = await self.get_session()
|
|
25
|
+
async with session.post(self.api + method, json=json) as response:
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
data: dict = await response.json()
|
|
28
|
+
return data.get("data")
|
|
29
|
+
|
|
30
|
+
async def request_bytes_file(self, url: str) -> bytes:
|
|
31
|
+
session = await self.get_session()
|
|
32
|
+
async with session.get(url) as response:
|
|
33
|
+
response.raise_for_status()
|
|
34
|
+
return await response.read()
|
|
35
|
+
|
|
36
|
+
async def request_upload_file(self, upload_url: str, file: Union[str], name: str) -> str:
|
|
37
|
+
session = await self.get_session()
|
|
38
|
+
|
|
39
|
+
if isinstance(file, str) and re.match(r"^https?://", file):
|
|
40
|
+
file = await self.request_bytes_file(file)
|
|
41
|
+
|
|
42
|
+
elif isinstance(file, str) and os.path.isfile(file):
|
|
43
|
+
async with aiofiles.open(file, "rb") as f:
|
|
44
|
+
file = await f.read()
|
|
45
|
+
|
|
46
|
+
form = FormData()
|
|
47
|
+
form.add_field("file", file, filename=name, content_type="application/octet-stream")
|
|
48
|
+
|
|
49
|
+
async with session.post(upload_url, data=form) as response:
|
|
50
|
+
response.raise_for_status()
|
|
51
|
+
data = await response.json()
|
|
52
|
+
return data["data"]["file_id"]
|
|
53
|
+
|
|
54
|
+
async def request_download_file(self, url: str, name: str) -> dict[str, Union[str, bool]]:
|
|
55
|
+
file = await self.request_bytes_file(url)
|
|
56
|
+
async with aiofiles.open(name, "wb") as f:
|
|
57
|
+
await f.write(file)
|
|
58
|
+
return {"status": True, "file": name}
|