RubigramClient 1.4.0__py3-none-any.whl → 1.4.2__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.
Potentially problematic release.
This version of RubigramClient might be problematic. Click here for more details.
- rubigram/__init__.py +2 -2
- rubigram/client.py +101 -45
- rubigram/filters.py +149 -97
- rubigram/method.py +88 -66
- rubigram/network.py +53 -25
- rubigram/types.py +222 -336
- {rubigramclient-1.4.0.dist-info → rubigramclient-1.4.2.dist-info}/METADATA +1 -1
- rubigramclient-1.4.2.dist-info/RECORD +11 -0
- rubigramclient-1.4.0.dist-info/RECORD +0 -11
- {rubigramclient-1.4.0.dist-info → rubigramclient-1.4.2.dist-info}/WHEEL +0 -0
- {rubigramclient-1.4.0.dist-info → rubigramclient-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {rubigramclient-1.4.0.dist-info → rubigramclient-1.4.2.dist-info}/top_level.txt +0 -0
rubigram/__init__.py
CHANGED
rubigram/client.py
CHANGED
|
@@ -1,90 +1,146 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import Callable, Awaitable
|
|
2
|
+
from typing import Optional, Callable, Awaitable
|
|
3
3
|
from aiohttp import web
|
|
4
4
|
from functools import wraps
|
|
5
5
|
from rubigram.types import Update, InlineMessage
|
|
6
6
|
from rubigram.method import Method
|
|
7
|
+
from rubigram.filters import Filter
|
|
8
|
+
from datetime import datetime
|
|
7
9
|
import asyncio
|
|
8
10
|
import logging
|
|
9
11
|
|
|
10
12
|
logger = logging.getLogger(__name__)
|
|
11
13
|
|
|
12
|
-
|
|
13
14
|
class Client(Method):
|
|
14
|
-
def __init__(
|
|
15
|
-
self,
|
|
16
|
-
token: str,
|
|
17
|
-
endpoint: Optional[str] = None,
|
|
18
|
-
host: str = "0.0.0.0",
|
|
19
|
-
port: int = 8000
|
|
20
|
-
):
|
|
15
|
+
def __init__(self, token: str, endpoint: Optional[str] = None, host: str = "0.0.0.0", port: int = 8000):
|
|
21
16
|
self.token = token
|
|
22
|
-
self.port = port
|
|
23
|
-
self.host = host
|
|
24
17
|
self.endpoint = endpoint
|
|
25
|
-
self.
|
|
26
|
-
self.
|
|
18
|
+
self.host = host
|
|
19
|
+
self.port = port
|
|
20
|
+
self.offset_id = None
|
|
27
21
|
self.routes = web.RouteTableDef()
|
|
22
|
+
self.message_handlers = []
|
|
23
|
+
self.inline_handlers = []
|
|
28
24
|
super().__init__(token)
|
|
29
25
|
|
|
30
|
-
def on_message(self, *filters:
|
|
26
|
+
def on_message(self, *filters: Filter):
|
|
31
27
|
def decorator(func: Callable[[Client, Update], Awaitable]):
|
|
32
28
|
@wraps(func)
|
|
33
29
|
async def wrapper(client: Client, update: Update):
|
|
34
30
|
try:
|
|
35
|
-
|
|
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
36
|
await func(client, update)
|
|
37
|
+
return True
|
|
38
|
+
return False
|
|
37
39
|
except Exception as e:
|
|
38
40
|
logger.exception(f"Error in message handler {func.__name__}: {e}")
|
|
39
|
-
|
|
41
|
+
return False
|
|
42
|
+
self.message_handlers.append(wrapper)
|
|
40
43
|
return func
|
|
41
44
|
return decorator
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
|
|
47
|
+
def on_inline_message(self, *filters: Filter):
|
|
44
48
|
def decorator(func: Callable[[Client, InlineMessage], Awaitable]):
|
|
45
49
|
@wraps(func)
|
|
46
50
|
async def wrapper(client: Client, update: InlineMessage):
|
|
47
51
|
try:
|
|
48
|
-
|
|
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):
|
|
49
57
|
await func(client, update)
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
50
60
|
except Exception as e:
|
|
51
61
|
logger.exception(f"Error in inline handler {func.__name__}: {e}")
|
|
52
|
-
|
|
62
|
+
return False
|
|
63
|
+
self.inline_handlers.append(wrapper)
|
|
53
64
|
return func
|
|
54
65
|
return decorator
|
|
55
66
|
|
|
56
|
-
|
|
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):
|
|
57
87
|
if "inline_message" in data:
|
|
58
|
-
event = InlineMessage.
|
|
59
|
-
await
|
|
88
|
+
event = InlineMessage.from_dict(data["inline_message"])
|
|
89
|
+
await self.dispatch_inline(event)
|
|
60
90
|
elif "update" in data:
|
|
61
|
-
event = Update.
|
|
62
|
-
|
|
63
|
-
|
|
91
|
+
event = Update.from_dict(data["update"])
|
|
92
|
+
event.client = self
|
|
93
|
+
await self.dispatch_update(event)
|
|
94
|
+
|
|
64
95
|
async def set_endpoints(self):
|
|
65
|
-
if
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await self.update_bot_endpoint(f"{self.endpoint}/ReceiveInlineMessage", "ReceiveInlineMessage")
|
|
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")
|
|
69
99
|
|
|
70
100
|
def run(self):
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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"})
|
|
76
113
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
82
136
|
|
|
83
|
-
|
|
84
|
-
|
|
137
|
+
now = int(datetime.now().timestamp())
|
|
138
|
+
if message_time >= now or message_time + 2 >= now:
|
|
139
|
+
await self.dispatch_update(update)
|
|
85
140
|
|
|
86
|
-
|
|
87
|
-
|
|
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)
|
|
88
145
|
|
|
89
|
-
|
|
90
|
-
web.run_app(app, host=self.host, port=self.port)
|
|
146
|
+
asyncio.run(polling())
|
rubigram/filters.py
CHANGED
|
@@ -1,113 +1,165 @@
|
|
|
1
1
|
from rubigram.types import Update, InlineMessage
|
|
2
|
-
|
|
3
|
-
from typing import Union
|
|
2
|
+
from typing import Union, Callable, Awaitable
|
|
4
3
|
import re
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# return user_state in states if isinstance(states, list) else user_state == states
|
|
11
|
-
# return filter
|
|
6
|
+
class Filter:
|
|
7
|
+
def __init__(self, func: Callable[[Union[Update, InlineMessage]], bool | Awaitable[bool]]):
|
|
8
|
+
self.func = func
|
|
12
9
|
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
if isinstance(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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()):
|
|
31
44
|
return True
|
|
32
|
-
|
|
33
|
-
return filter
|
|
45
|
+
return False
|
|
34
46
|
|
|
35
|
-
def chat(chat_id: Union[str, list[str]]):
|
|
36
|
-
def filter(message: Union[Update, InlineMessage]):
|
|
37
|
-
chat_ids = chat_id if isinstance(chat_id, list) else [chat_id]
|
|
38
|
-
if isinstance(message, Update) or isinstance(message, InlineMessage):
|
|
39
|
-
return message.chat_id in chat_ids
|
|
40
|
-
return False
|
|
41
|
-
return filter
|
|
42
|
-
|
|
43
|
-
def regex(pattern: str):
|
|
44
|
-
def filter(message: Union[Update, InlineMessage]):
|
|
45
|
-
if isinstance(message, Update) and message.type == "NewMessage" and message.new_message.text:
|
|
46
|
-
return bool(re.search(pattern, message.new_message.text))
|
|
47
|
-
elif isinstance(message, InlineMessage) and message.text:
|
|
48
|
-
return bool(re.search(pattern, message.text))
|
|
49
|
-
return False
|
|
50
|
-
return filter
|
|
51
|
-
|
|
52
|
-
def text():
|
|
53
|
-
def filter(message: Update):
|
|
54
|
-
if isinstance(message, Update) and message.type == "NewMessage":
|
|
55
|
-
return message.new_message.text is None
|
|
56
|
-
return False
|
|
57
|
-
return filter
|
|
58
47
|
|
|
59
|
-
|
|
60
|
-
def
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return False
|
|
64
|
-
return filter
|
|
48
|
+
class regex(Filter):
|
|
49
|
+
def __init__(self, pattern: str):
|
|
50
|
+
self.pattern = pattern
|
|
51
|
+
super().__init__(self.filter)
|
|
65
52
|
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
-
if isinstance(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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", "")
|
|
72
62
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if isinstance(message, Update) and message.type == "NewMessage":
|
|
76
|
-
return message.new_message.forwarded_from is None
|
|
63
|
+
if text:
|
|
64
|
+
return bool(re.search(self.pattern, text))
|
|
77
65
|
return False
|
|
78
|
-
return filter
|
|
79
66
|
|
|
80
|
-
def location():
|
|
81
|
-
def filter(message: Update):
|
|
82
|
-
if isinstance(message, Update) and message.type == "NewMessage":
|
|
83
|
-
return message.new_message.location is None
|
|
84
|
-
return False
|
|
85
|
-
return filter
|
|
86
67
|
|
|
87
|
-
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return False
|
|
92
|
-
return filter
|
|
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)
|
|
93
72
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return False
|
|
99
|
-
return filter
|
|
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
|
|
100
77
|
|
|
101
|
-
def poll():
|
|
102
|
-
def filter(message: Update):
|
|
103
|
-
if isinstance(message, Update) and message.type == "NewMessage":
|
|
104
|
-
return message.new_message.poll is None
|
|
105
|
-
return False
|
|
106
|
-
return filter
|
|
107
78
|
|
|
108
|
-
|
|
109
|
-
def
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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)
|