maxapi-python 1.2.4__py3-none-any.whl → 2.0.0__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.
- maxapi_python-2.0.0.dist-info/METADATA +217 -0
- maxapi_python-2.0.0.dist-info/RECORD +140 -0
- {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/WHEEL +1 -1
- pymax/__init__.py +50 -105
- pymax/api/__init__.py +17 -0
- pymax/api/auth/__init__.py +1 -0
- pymax/api/auth/enums.py +17 -0
- pymax/api/auth/payloads.py +129 -0
- pymax/api/auth/service.py +313 -0
- pymax/api/auth/types.py +13 -0
- pymax/api/chats/__init__.py +8 -0
- pymax/api/chats/enums.py +27 -0
- pymax/api/chats/payloads.py +103 -0
- pymax/api/chats/service.py +277 -0
- pymax/api/facade.py +32 -0
- pymax/api/messages/__init__.py +1 -0
- pymax/api/messages/enums.py +17 -0
- pymax/api/messages/payloads.py +92 -0
- pymax/api/messages/service.py +337 -0
- pymax/api/models.py +13 -0
- pymax/api/response.py +123 -0
- pymax/api/self/__init__.py +2 -0
- pymax/api/self/enums.py +11 -0
- pymax/api/self/payloads.py +41 -0
- pymax/api/self/service.py +142 -0
- pymax/api/session/__init__.py +1 -0
- pymax/api/session/enums.py +10 -0
- pymax/api/session/payloads.py +76 -0
- pymax/api/session/service.py +72 -0
- pymax/api/uploads/__init__.py +1 -0
- pymax/api/uploads/models.py +49 -0
- pymax/api/uploads/payloads.py +25 -0
- pymax/api/uploads/service.py +458 -0
- pymax/api/users/__init__.py +2 -0
- pymax/api/users/enums.py +12 -0
- pymax/api/users/payloads.py +16 -0
- pymax/api/users/service.py +124 -0
- pymax/app.py +273 -0
- pymax/auth/__init__.py +25 -0
- pymax/auth/base.py +37 -0
- pymax/auth/email.py +0 -0
- pymax/auth/models.py +5 -0
- pymax/auth/providers.py +127 -0
- pymax/auth/qr.py +135 -0
- pymax/auth/service.py +25 -0
- pymax/auth/sms.py +122 -0
- pymax/base.py +204 -0
- pymax/client.py +106 -0
- pymax/client_web.py +83 -0
- pymax/config.py +215 -0
- pymax/connection/__init__.py +1 -0
- pymax/connection/connection.py +205 -0
- pymax/connection/pending.py +46 -0
- pymax/connection/readers/__init__.py +2 -0
- pymax/connection/readers/base.py +6 -0
- pymax/connection/readers/tcp.py +29 -0
- pymax/connection/readers/ws.py +14 -0
- pymax/dispatch/__init__.py +10 -0
- pymax/dispatch/dispatcher.py +222 -0
- pymax/dispatch/enums.py +12 -0
- pymax/dispatch/mapping.py +73 -0
- pymax/dispatch/resolvers.py +52 -0
- pymax/dispatch/router.py +216 -0
- pymax/exceptions.py +22 -89
- pymax/files/__init__.py +9 -0
- pymax/files/base.py +82 -0
- pymax/files/file.py +76 -0
- pymax/files/photo.py +108 -0
- pymax/files/static.py +10 -0
- pymax/files/video.py +74 -0
- pymax/formatting/__init__.py +0 -0
- pymax/formatting/markdown.py +217 -0
- pymax/infra/__init__.py +1 -0
- pymax/infra/auth.py +55 -0
- pymax/infra/base.py +15 -0
- pymax/infra/chat.py +240 -0
- pymax/infra/message.py +252 -0
- pymax/infra/protocol.py +9 -0
- pymax/infra/self.py +139 -0
- pymax/infra/user.py +107 -0
- pymax/logging.py +129 -0
- pymax/protocol/__init__.py +11 -0
- pymax/protocol/base.py +13 -0
- pymax/{static/enum.py → protocol/enums.py} +36 -79
- pymax/protocol/models.py +33 -0
- pymax/protocol/tcp/__init__.py +1 -0
- pymax/protocol/tcp/compression.py +97 -0
- pymax/protocol/tcp/framing.py +68 -0
- pymax/protocol/tcp/payload.py +127 -0
- pymax/protocol/tcp/protocol.py +68 -0
- pymax/protocol/ws/__init__.py +1 -0
- pymax/protocol/ws/protocol.py +27 -0
- pymax/py.typed +0 -0
- pymax/routers.py +8 -0
- pymax/session/__init__.py +3 -0
- pymax/session/models.py +11 -0
- pymax/session/protocol.py +14 -0
- pymax/session/store.py +232 -0
- pymax/telemetry/__init__.py +3 -0
- pymax/telemetry/navigation.py +181 -0
- pymax/telemetry/payloads.py +142 -0
- pymax/telemetry/service.py +225 -0
- pymax/transport/__init__.py +0 -0
- pymax/transport/base.py +14 -0
- pymax/transport/tcp.py +93 -0
- pymax/transport/websocket.py +50 -0
- pymax/types/__init__.py +2 -0
- pymax/types/domain/__init__.py +11 -0
- pymax/types/domain/attachments/__init__.py +11 -0
- pymax/types/domain/attachments/audio.py +35 -0
- pymax/types/domain/attachments/call.py +26 -0
- pymax/types/domain/attachments/contact.py +32 -0
- pymax/types/domain/attachments/control.py +20 -0
- pymax/types/domain/attachments/enums.py +27 -0
- pymax/types/domain/attachments/file.py +56 -0
- pymax/types/domain/attachments/keyboards/__init__.py +1 -0
- pymax/types/domain/attachments/keyboards/inline.py +19 -0
- pymax/types/domain/attachments/photo.py +45 -0
- pymax/types/domain/attachments/share.py +29 -0
- pymax/types/domain/attachments/sticker.py +50 -0
- pymax/types/domain/attachments/video.py +90 -0
- pymax/types/domain/auth.py +161 -0
- pymax/types/domain/base.py +17 -0
- pymax/types/domain/chat.py +426 -0
- pymax/types/domain/element.py +24 -0
- pymax/types/domain/enums.py +24 -0
- pymax/types/domain/error.py +20 -0
- pymax/types/domain/folder.py +74 -0
- pymax/types/domain/login.py +35 -0
- pymax/types/domain/message.py +378 -0
- pymax/types/domain/name.py +20 -0
- pymax/types/domain/profile.py +15 -0
- pymax/types/domain/session.py +52 -0
- pymax/types/domain/sync.py +80 -0
- pymax/types/domain/user.py +117 -0
- pymax/types/events/__init__.py +3 -0
- pymax/types/events/file.py +5 -0
- pymax/types/events/message.py +37 -0
- pymax/types/events/video.py +5 -0
- maxapi_python-1.2.4.dist-info/METADATA +0 -205
- maxapi_python-1.2.4.dist-info/RECORD +0 -33
- pymax/core.py +0 -390
- pymax/crud.py +0 -96
- pymax/files.py +0 -138
- pymax/filters.py +0 -164
- pymax/formatter.py +0 -31
- pymax/formatting.py +0 -74
- pymax/interfaces.py +0 -552
- pymax/mixins/__init__.py +0 -40
- pymax/mixins/auth.py +0 -368
- pymax/mixins/channel.py +0 -130
- pymax/mixins/group.py +0 -458
- pymax/mixins/handler.py +0 -285
- pymax/mixins/message.py +0 -879
- pymax/mixins/scheduler.py +0 -28
- pymax/mixins/self.py +0 -259
- pymax/mixins/socket.py +0 -297
- pymax/mixins/telemetry.py +0 -112
- pymax/mixins/user.py +0 -219
- pymax/mixins/websocket.py +0 -142
- pymax/models.py +0 -8
- pymax/navigation.py +0 -187
- pymax/payloads.py +0 -367
- pymax/protocols.py +0 -123
- pymax/static/constant.py +0 -89
- pymax/types.py +0 -1220
- pymax/utils.py +0 -90
- {maxapi_python-1.2.4.dist-info → maxapi_python-2.0.0.dist-info}/licenses/LICENSE +0 -0
pymax/crud.py
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
from typing import cast
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
|
|
4
|
-
from sqlalchemy.engine.base import Engine
|
|
5
|
-
from sqlmodel import Session, SQLModel, create_engine, select
|
|
6
|
-
|
|
7
|
-
from .models import Auth
|
|
8
|
-
from .static.enum import DeviceType
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Database:
|
|
12
|
-
def __init__(self, workdir: str) -> None:
|
|
13
|
-
self.workdir = workdir
|
|
14
|
-
self.engine = self.get_engine(workdir)
|
|
15
|
-
self.create_all()
|
|
16
|
-
self._ensure_single_auth()
|
|
17
|
-
|
|
18
|
-
def create_all(self) -> None:
|
|
19
|
-
SQLModel.metadata.create_all(self.engine)
|
|
20
|
-
|
|
21
|
-
def get_engine(self, workdir: str) -> Engine:
|
|
22
|
-
return create_engine(f"sqlite:///{workdir}/session.db")
|
|
23
|
-
|
|
24
|
-
def get_session(self) -> Session:
|
|
25
|
-
return Session(bind=self.engine)
|
|
26
|
-
|
|
27
|
-
def get_auth_token(self) -> str | None:
|
|
28
|
-
with self.get_session() as session:
|
|
29
|
-
token = cast(str | None, session.exec(select(Auth.token)).first())
|
|
30
|
-
return token
|
|
31
|
-
|
|
32
|
-
def get_device_id(self) -> UUID:
|
|
33
|
-
with self.get_session() as session:
|
|
34
|
-
device_id = session.exec(select(Auth.device_id)).first()
|
|
35
|
-
|
|
36
|
-
if device_id is None:
|
|
37
|
-
auth = Auth()
|
|
38
|
-
session.add(auth)
|
|
39
|
-
session.commit()
|
|
40
|
-
session.refresh(auth)
|
|
41
|
-
return auth.device_id
|
|
42
|
-
return device_id
|
|
43
|
-
|
|
44
|
-
def insert_auth(self, auth: Auth) -> Auth:
|
|
45
|
-
with self.get_session() as session:
|
|
46
|
-
session.add(auth)
|
|
47
|
-
session.commit()
|
|
48
|
-
session.refresh(auth)
|
|
49
|
-
return auth
|
|
50
|
-
|
|
51
|
-
def update_auth_token(self, device_id: UUID, token: str) -> None:
|
|
52
|
-
with self.get_session() as session:
|
|
53
|
-
auth = session.exec(select(Auth).where(Auth.device_id == device_id)).first()
|
|
54
|
-
if auth:
|
|
55
|
-
auth.token = token
|
|
56
|
-
session.add(auth)
|
|
57
|
-
session.commit()
|
|
58
|
-
session.refresh(auth)
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
existing = session.exec(select(Auth)).first()
|
|
62
|
-
if existing:
|
|
63
|
-
existing.device_id = device_id
|
|
64
|
-
existing.token = token
|
|
65
|
-
session.add(existing)
|
|
66
|
-
session.commit()
|
|
67
|
-
session.refresh(existing)
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
new_auth = Auth(device_id=device_id, token=token)
|
|
71
|
-
session.add(new_auth)
|
|
72
|
-
session.commit()
|
|
73
|
-
session.refresh(new_auth)
|
|
74
|
-
|
|
75
|
-
def update(self, auth: Auth) -> Auth:
|
|
76
|
-
with self.get_session() as session:
|
|
77
|
-
session.add(auth)
|
|
78
|
-
session.commit()
|
|
79
|
-
session.refresh(auth)
|
|
80
|
-
return auth
|
|
81
|
-
|
|
82
|
-
def _ensure_single_auth(self) -> None:
|
|
83
|
-
with self.get_session() as session:
|
|
84
|
-
rows = session.exec(select(Auth)).all()
|
|
85
|
-
if not rows:
|
|
86
|
-
auth = Auth(device_type=DeviceType.WEB.value)
|
|
87
|
-
session.add(auth)
|
|
88
|
-
session.commit()
|
|
89
|
-
session.refresh(auth)
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
if len(rows) > 1:
|
|
93
|
-
_ = rows[0]
|
|
94
|
-
for extra in rows[1:]:
|
|
95
|
-
session.delete(extra)
|
|
96
|
-
session.commit()
|
pymax/files.py
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import mimetypes
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import ClassVar
|
|
5
|
-
|
|
6
|
-
from aiofiles import open as aio_open
|
|
7
|
-
from aiohttp import ClientSession
|
|
8
|
-
from typing_extensions import override
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class BaseFile(ABC):
|
|
12
|
-
def __init__(
|
|
13
|
-
self, raw: bytes | None = None, *, url: str | None = None, path: str | None = None
|
|
14
|
-
) -> None:
|
|
15
|
-
self.raw = raw
|
|
16
|
-
self.url = url
|
|
17
|
-
self.path = path
|
|
18
|
-
|
|
19
|
-
if self.url is None and self.path is None:
|
|
20
|
-
raise ValueError("Either url or path must be provided.")
|
|
21
|
-
|
|
22
|
-
if self.url and self.path:
|
|
23
|
-
raise ValueError("Only one of url or path must be provided.")
|
|
24
|
-
|
|
25
|
-
@abstractmethod
|
|
26
|
-
async def read(self) -> bytes:
|
|
27
|
-
if self.raw is not None:
|
|
28
|
-
return self.raw
|
|
29
|
-
|
|
30
|
-
if self.url:
|
|
31
|
-
async with (
|
|
32
|
-
ClientSession() as session,
|
|
33
|
-
session.get(self.url) as response,
|
|
34
|
-
):
|
|
35
|
-
response.raise_for_status()
|
|
36
|
-
return await response.read()
|
|
37
|
-
elif self.path:
|
|
38
|
-
async with aio_open(self.path, "rb") as f:
|
|
39
|
-
return await f.read()
|
|
40
|
-
else:
|
|
41
|
-
raise ValueError("Either url or path must be provided.")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class Photo(BaseFile):
|
|
45
|
-
ALLOWED_EXTENSIONS: ClassVar[set[str]] = {
|
|
46
|
-
".jpg",
|
|
47
|
-
".jpeg",
|
|
48
|
-
".png",
|
|
49
|
-
".gif",
|
|
50
|
-
".webp",
|
|
51
|
-
".bmp",
|
|
52
|
-
} # FIXME: костыль ✅
|
|
53
|
-
|
|
54
|
-
def __init__(
|
|
55
|
-
self,
|
|
56
|
-
raw: bytes | None = None,
|
|
57
|
-
*,
|
|
58
|
-
url: str | None = None,
|
|
59
|
-
path: str | None = None,
|
|
60
|
-
name: str | None = None,
|
|
61
|
-
) -> None:
|
|
62
|
-
if path:
|
|
63
|
-
self.file_name = Path(path).name
|
|
64
|
-
elif url:
|
|
65
|
-
self.file_name = Path(url).name
|
|
66
|
-
elif name:
|
|
67
|
-
self.file_name = name
|
|
68
|
-
else:
|
|
69
|
-
self.file_name = ""
|
|
70
|
-
|
|
71
|
-
super().__init__(raw=raw, url=url, path=path)
|
|
72
|
-
|
|
73
|
-
def validate_photo(self) -> tuple[str, str] | None:
|
|
74
|
-
if self.path:
|
|
75
|
-
extension = Path(self.path).suffix.lower()
|
|
76
|
-
if extension not in self.ALLOWED_EXTENSIONS:
|
|
77
|
-
raise ValueError(
|
|
78
|
-
f"Invalid photo extension: {extension}. Allowed: {self.ALLOWED_EXTENSIONS}"
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
return (extension[1:], ("image/" + extension[1:]).lower())
|
|
82
|
-
elif self.url:
|
|
83
|
-
extension = Path(self.url).suffix.lower()
|
|
84
|
-
if extension not in self.ALLOWED_EXTENSIONS:
|
|
85
|
-
raise ValueError(
|
|
86
|
-
f"Invalid photo extension in URL: {extension}. Allowed: {self.ALLOWED_EXTENSIONS}"
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
mime_type = mimetypes.guess_type(self.url)[0]
|
|
90
|
-
|
|
91
|
-
if not mime_type or not mime_type.startswith("image/"):
|
|
92
|
-
raise ValueError(f"URL does not appear to be an image: {self.url}")
|
|
93
|
-
|
|
94
|
-
return (extension[1:], mime_type)
|
|
95
|
-
return None
|
|
96
|
-
|
|
97
|
-
@override
|
|
98
|
-
async def read(self) -> bytes:
|
|
99
|
-
return await super().read()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class Video(BaseFile):
|
|
103
|
-
def __init__(
|
|
104
|
-
self, raw: bytes | None = None, *, url: str | None = None, path: str | None = None
|
|
105
|
-
) -> None:
|
|
106
|
-
self.file_name: str = ""
|
|
107
|
-
if path:
|
|
108
|
-
self.file_name = Path(path).name
|
|
109
|
-
elif url:
|
|
110
|
-
self.file_name = Path(url).name
|
|
111
|
-
|
|
112
|
-
if not self.file_name:
|
|
113
|
-
raise ValueError("Either url or path must be provided.")
|
|
114
|
-
super().__init__(raw=raw, url=url, path=path)
|
|
115
|
-
|
|
116
|
-
@override
|
|
117
|
-
async def read(self) -> bytes:
|
|
118
|
-
return await super().read()
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class File(BaseFile):
|
|
122
|
-
def __init__(
|
|
123
|
-
self, raw: bytes | None = None, *, url: str | None = None, path: str | None = None
|
|
124
|
-
) -> None:
|
|
125
|
-
self.file_name: str = ""
|
|
126
|
-
if path:
|
|
127
|
-
self.file_name = Path(path).name
|
|
128
|
-
elif url:
|
|
129
|
-
self.file_name = Path(url).name
|
|
130
|
-
|
|
131
|
-
if not self.file_name:
|
|
132
|
-
raise ValueError("Either url or path must be provided.")
|
|
133
|
-
|
|
134
|
-
super().__init__(raw=raw, url=url, path=path)
|
|
135
|
-
|
|
136
|
-
@override
|
|
137
|
-
async def read(self) -> bytes:
|
|
138
|
-
return await super().read()
|
pymax/filters.py
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from abc import ABC, abstractmethod
|
|
5
|
-
from typing import Generic, TypeVar
|
|
6
|
-
|
|
7
|
-
from pymax.static.enum import AttachType, ChatType, MessageStatus
|
|
8
|
-
from pymax.types import Message
|
|
9
|
-
|
|
10
|
-
T_co = TypeVar("T_co")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class BaseFilter(ABC, Generic[T_co]):
|
|
14
|
-
event_type: type[T_co]
|
|
15
|
-
|
|
16
|
-
@abstractmethod
|
|
17
|
-
def __call__(self, event: T_co) -> bool: ...
|
|
18
|
-
|
|
19
|
-
def __and__(self, other: BaseFilter[T_co]) -> BaseFilter[T_co]:
|
|
20
|
-
return AndFilter(self, other)
|
|
21
|
-
|
|
22
|
-
def __or__(self, other: BaseFilter[T_co]) -> BaseFilter[T_co]:
|
|
23
|
-
return OrFilter(self, other)
|
|
24
|
-
|
|
25
|
-
def __invert__(self) -> BaseFilter[T_co]:
|
|
26
|
-
return NotFilter(self)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class AndFilter(BaseFilter[T_co]):
|
|
30
|
-
def __init__(self, *filters: BaseFilter[T_co]) -> None:
|
|
31
|
-
self.filters = filters
|
|
32
|
-
self.event_type = filters[0].event_type
|
|
33
|
-
|
|
34
|
-
def __call__(self, event: T_co) -> bool:
|
|
35
|
-
return all(f(event) for f in self.filters)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class OrFilter(BaseFilter[T_co]):
|
|
39
|
-
def __init__(self, *filters: BaseFilter[T_co]) -> None:
|
|
40
|
-
self.filters = filters
|
|
41
|
-
self.event_type = filters[0].event_type
|
|
42
|
-
|
|
43
|
-
def __call__(self, event: T_co) -> bool:
|
|
44
|
-
return any(f(event) for f in self.filters)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class NotFilter(BaseFilter[T_co]):
|
|
48
|
-
def __init__(self, base_filter: BaseFilter[T_co]) -> None:
|
|
49
|
-
self.base_filter = base_filter
|
|
50
|
-
self.event_type = base_filter.event_type
|
|
51
|
-
|
|
52
|
-
def __call__(self, event: T_co) -> bool:
|
|
53
|
-
return not self.base_filter(event)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class ChatFilter(BaseFilter[Message]):
|
|
57
|
-
event_type = Message
|
|
58
|
-
|
|
59
|
-
def __init__(self, chat_id: int) -> None:
|
|
60
|
-
self.chat_id = chat_id
|
|
61
|
-
|
|
62
|
-
def __call__(self, message: Message) -> bool:
|
|
63
|
-
return message.chat_id == self.chat_id
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class TextFilter(BaseFilter[Message]):
|
|
67
|
-
event_type = Message
|
|
68
|
-
|
|
69
|
-
def __init__(self, text: str) -> None:
|
|
70
|
-
self.text = text
|
|
71
|
-
|
|
72
|
-
def __call__(self, message: Message) -> bool:
|
|
73
|
-
return self.text in message.text
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class SenderFilter(BaseFilter[Message]):
|
|
77
|
-
event_type = Message
|
|
78
|
-
|
|
79
|
-
def __init__(self, user_id: int) -> None:
|
|
80
|
-
self.user_id = user_id
|
|
81
|
-
|
|
82
|
-
def __call__(self, message: Message) -> bool:
|
|
83
|
-
return message.sender == self.user_id
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class StatusFilter(BaseFilter[Message]):
|
|
87
|
-
event_type = Message
|
|
88
|
-
|
|
89
|
-
def __init__(self, status: MessageStatus) -> None:
|
|
90
|
-
self.status = status
|
|
91
|
-
|
|
92
|
-
def __call__(self, message: Message) -> bool:
|
|
93
|
-
return message.status == self.status
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
class TextContainsFilter(BaseFilter[Message]):
|
|
97
|
-
event_type = Message
|
|
98
|
-
|
|
99
|
-
def __init__(self, substring: str) -> None:
|
|
100
|
-
self.substring = substring
|
|
101
|
-
|
|
102
|
-
def __call__(self, message: Message) -> bool:
|
|
103
|
-
return self.substring in message.text
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
class RegexTextFilter(BaseFilter[Message]):
|
|
107
|
-
event_type = Message
|
|
108
|
-
|
|
109
|
-
def __init__(self, pattern: str) -> None:
|
|
110
|
-
self.pattern = pattern
|
|
111
|
-
self.regex = re.compile(pattern)
|
|
112
|
-
|
|
113
|
-
def __call__(self, message: Message) -> bool:
|
|
114
|
-
return bool(self.regex.search(message.text))
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
class MediaFilter(BaseFilter[Message]):
|
|
118
|
-
event_type = Message
|
|
119
|
-
|
|
120
|
-
def __call__(self, message: Message) -> bool:
|
|
121
|
-
return message.attaches is not None and len(message.attaches) > 0
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
class FileFilter(BaseFilter[Message]):
|
|
125
|
-
event_type = Message
|
|
126
|
-
|
|
127
|
-
def __call__(self, message: Message) -> bool:
|
|
128
|
-
if message.attaches is None:
|
|
129
|
-
return False
|
|
130
|
-
return any(attach.type == AttachType.FILE for attach in message.attaches)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class Filters:
|
|
134
|
-
@staticmethod
|
|
135
|
-
def chat(chat_id: int) -> BaseFilter[Message]:
|
|
136
|
-
return ChatFilter(chat_id)
|
|
137
|
-
|
|
138
|
-
@staticmethod
|
|
139
|
-
def text(text: str) -> BaseFilter[Message]:
|
|
140
|
-
return TextFilter(text)
|
|
141
|
-
|
|
142
|
-
@staticmethod
|
|
143
|
-
def sender(user_id: int) -> BaseFilter[Message]:
|
|
144
|
-
return SenderFilter(user_id)
|
|
145
|
-
|
|
146
|
-
@staticmethod
|
|
147
|
-
def status(status: MessageStatus) -> BaseFilter[Message]:
|
|
148
|
-
return StatusFilter(status)
|
|
149
|
-
|
|
150
|
-
@staticmethod
|
|
151
|
-
def text_contains(substring: str) -> BaseFilter[Message]:
|
|
152
|
-
return TextContainsFilter(substring)
|
|
153
|
-
|
|
154
|
-
@staticmethod
|
|
155
|
-
def text_matches(pattern: str) -> BaseFilter[Message]:
|
|
156
|
-
return RegexTextFilter(pattern)
|
|
157
|
-
|
|
158
|
-
@staticmethod
|
|
159
|
-
def has_media() -> BaseFilter[Message]:
|
|
160
|
-
return MediaFilter()
|
|
161
|
-
|
|
162
|
-
@staticmethod
|
|
163
|
-
def has_file() -> BaseFilter[Message]:
|
|
164
|
-
return FileFilter()
|
pymax/formatter.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import ClassVar
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class ColoredFormatter(logging.Formatter):
|
|
6
|
-
COLORS: ClassVar = {
|
|
7
|
-
"DEBUG": "\033[37m",
|
|
8
|
-
"INFO": "\033[36m",
|
|
9
|
-
"WARNING": "\033[33m",
|
|
10
|
-
"ERROR": "\033[31m",
|
|
11
|
-
"CRITICAL": "\033[41m",
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
RESET = "\033[0m"
|
|
15
|
-
DIM = "\033[2m"
|
|
16
|
-
BOLD = "\033[1m"
|
|
17
|
-
|
|
18
|
-
def format(self, record: logging.LogRecord) -> str:
|
|
19
|
-
level_color = self.COLORS.get(record.levelname, self.RESET)
|
|
20
|
-
time_color = self.DIM
|
|
21
|
-
name_color = "\033[35m"
|
|
22
|
-
message_color = self.RESET
|
|
23
|
-
|
|
24
|
-
log = (
|
|
25
|
-
f"{time_color}{self.formatTime(record, '%H:%M:%S')}{self.RESET} "
|
|
26
|
-
f"[{level_color}{record.levelname}{self.RESET}] "
|
|
27
|
-
f"{name_color}{record.name}{self.RESET}: "
|
|
28
|
-
f"{message_color}{record.getMessage()}{self.RESET}"
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
return log
|
pymax/formatting.py
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from pymax.static.enum import FormattingType
|
|
4
|
-
from pymax.types import Element
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Formatting:
|
|
8
|
-
MARKUP_BLOCK_PATTERN = re.compile(
|
|
9
|
-
(
|
|
10
|
-
r"\*\*(?P<strong>.+?)\*\*|"
|
|
11
|
-
r"\*(?P<italic>.+?)\*|"
|
|
12
|
-
r"__(?P<underline>.+?)__|"
|
|
13
|
-
r"~~(?P<strike>.+?)~~"
|
|
14
|
-
),
|
|
15
|
-
re.DOTALL,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
@staticmethod
|
|
19
|
-
def get_elements_from_markdown(text: str) -> tuple[list[Element], str]:
|
|
20
|
-
text = text.strip("\n")
|
|
21
|
-
elements: list[Element] = []
|
|
22
|
-
clean_parts: list[str] = []
|
|
23
|
-
current_pos = 0
|
|
24
|
-
|
|
25
|
-
last_end = 0
|
|
26
|
-
for match in Formatting.MARKUP_BLOCK_PATTERN.finditer(text):
|
|
27
|
-
between = text[last_end : match.start()]
|
|
28
|
-
if between:
|
|
29
|
-
clean_parts.append(between)
|
|
30
|
-
current_pos += len(between)
|
|
31
|
-
|
|
32
|
-
inner_text = None
|
|
33
|
-
fmt_type = None
|
|
34
|
-
if match.group("strong") is not None:
|
|
35
|
-
inner_text = match.group("strong")
|
|
36
|
-
fmt_type = FormattingType.STRONG
|
|
37
|
-
elif match.group("italic") is not None:
|
|
38
|
-
inner_text = match.group("italic")
|
|
39
|
-
fmt_type = FormattingType.EMPHASIZED
|
|
40
|
-
elif match.group("underline") is not None:
|
|
41
|
-
inner_text = match.group("underline")
|
|
42
|
-
fmt_type = FormattingType.UNDERLINE
|
|
43
|
-
elif match.group("strike") is not None:
|
|
44
|
-
inner_text = match.group("strike")
|
|
45
|
-
fmt_type = FormattingType.STRIKETHROUGH
|
|
46
|
-
|
|
47
|
-
if inner_text is not None and fmt_type is not None:
|
|
48
|
-
next_pos = match.end()
|
|
49
|
-
has_newline = (next_pos < len(text) and text[next_pos] == "\n") or (
|
|
50
|
-
next_pos == len(text)
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
length = len(inner_text) + (1 if has_newline else 0)
|
|
54
|
-
elements.append(Element(type=fmt_type, from_=current_pos, length=length))
|
|
55
|
-
|
|
56
|
-
clean_parts.append(inner_text)
|
|
57
|
-
if has_newline:
|
|
58
|
-
clean_parts.append("\n")
|
|
59
|
-
|
|
60
|
-
current_pos += length
|
|
61
|
-
|
|
62
|
-
if next_pos < len(text) and text[next_pos] == "\n":
|
|
63
|
-
last_end = match.end() + 1
|
|
64
|
-
else:
|
|
65
|
-
last_end = match.end()
|
|
66
|
-
else:
|
|
67
|
-
last_end = match.end()
|
|
68
|
-
|
|
69
|
-
tail = text[last_end:]
|
|
70
|
-
if tail:
|
|
71
|
-
clean_parts.append(tail)
|
|
72
|
-
|
|
73
|
-
clean_text = "".join(clean_parts)
|
|
74
|
-
return elements, clean_text
|