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 CHANGED
@@ -1,4 +1,4 @@
1
- from .client import Client
1
+ from .network import Network
2
2
  from .method import Method
3
- from .network import NetWork
3
+ from .client import Client
4
4
  from . import filters
rubigram/client.py CHANGED
@@ -1,90 +1,146 @@
1
1
  from __future__ import annotations
2
- from typing import Callable, Awaitable, Optional
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.messages_handler: list[Callable[[Client, Update], Awaitable]] = []
26
- self.inlines_handler: list[Callable[[Client, InlineMessage], Awaitable]] = []
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: Callable[[Update], bool]):
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
- if all(f(update) for f in filters):
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
- self.messages_handler.append(wrapper)
41
+ return False
42
+ self.message_handlers.append(wrapper)
40
43
  return func
41
44
  return decorator
42
45
 
43
- def on_inline_message(self, *filters: Callable[[InlineMessage], bool]):
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
- if all(f(update) for f in filters):
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
- self.inlines_handler.append(wrapper)
62
+ return False
63
+ self.inline_handlers.append(wrapper)
53
64
  return func
54
65
  return decorator
55
66
 
56
- async def handle_update(self, data: dict):
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.read(data["inline_message"])
59
- await asyncio.gather(*(h(self, event) for h in self.inlines_handler))
88
+ event = InlineMessage.from_dict(data["inline_message"])
89
+ await self.dispatch_inline(event)
60
90
  elif "update" in data:
61
- event = Update.read(data["update"], self)
62
- await asyncio.gather(*(h(self, event) for h in self.messages_handler))
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 not self.endpoint:
66
- return
67
- await self.update_bot_endpoint(f"{self.endpoint}/ReceiveUpdate", "ReceiveUpdate")
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
- @self.routes.post("/ReceiveUpdate")
72
- async def receive_update(request: web.Request):
73
- data = await request.json()
74
- await self.handle_update(data)
75
- return web.json_response({"status": "OK"})
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
- @self.routes.post("/ReceiveInlineMessage")
78
- async def receive_inline_message(request: web.Request):
79
- data = await request.json()
80
- await self.handle_update(data)
81
- return web.json_response({"status": "OK"})
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
- app = web.Application()
84
- app.add_routes(self.routes)
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
- async def on_startup(_):
87
- await self.set_endpoints()
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
- app.on_startup.append(on_startup)
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
- # from rubigram.state import state_manager
3
- from typing import Union
2
+ from typing import Union, Callable, Awaitable
4
3
  import re
5
4
 
6
5
 
7
- # def state(states: Union[str, list[str]]):
8
- # def filter(message: Union[Update, InlineMessage]):
9
- # user_state = state_manager.get_state(message.chat_id)
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 command(commands: Union[str, list[str]], prefix: str = "/"):
14
- def filter(message: Update):
15
- if isinstance(message, Update) and message.new_message and message.new_message.text:
16
- text = message.new_message.text.strip()
17
- cmds = commands if isinstance(commands, list) else [commands]
18
- for cmd in cmds:
19
- if text.lower().startswith(prefix + cmd.lower()):
20
- return True
21
- return False
22
- return filter
23
-
24
- def button(id: Union[str, list[str]]):
25
- def filter(message: InlineMessage):
26
- if isinstance(message, InlineMessage):
27
- button_id = message.aux_data.button_id
28
- ID = id if isinstance(id, list) else [id]
29
- for i in ID:
30
- if button_id == i:
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
- return False
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
- def file():
60
- def filter(message: Update):
61
- if isinstance(message, Update) and message.type == "NewMessage":
62
- return message.new_message.file is None
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 private():
67
- def filter(message: Update):
68
- if isinstance(message, Update) and message.type == "NewMessage":
69
- return message.new_message.sender_type in ["User", "Bot"]
70
- return False
71
- return filter
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
- def forward():
74
- def filter(message: Update):
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
- def sticker():
88
- def filter(message: Update):
89
- if isinstance(message, Update) and message.type == "NewMessage":
90
- return message.new_message.sticker is None
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 contact():
95
- def filter(message: Update):
96
- if isinstance(message, Update) and message.type == "NewMessage":
97
- return message.new_message.contact_message is None
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
- def live():
109
- def filter(message: Update):
110
- if isinstance(message, Update) and message.type == "NewMessage":
111
- return message.new_message.live_location is None
112
- return False
113
- return filter
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)