maxapi-python 2.2.0__py3-none-any.whl → 2.3.1__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.2.0.dist-info → maxapi_python-2.3.1.dist-info}/METADATA +2 -1
- {maxapi_python-2.2.0.dist-info → maxapi_python-2.3.1.dist-info}/RECORD +28 -28
- pymax/__init__.py +1 -1
- pymax/api/chats/payloads.py +6 -0
- pymax/api/chats/service.py +18 -0
- pymax/api/messages/payloads.py +21 -1
- pymax/api/messages/service.py +42 -11
- pymax/api/users/payloads.py +22 -0
- pymax/api/users/service.py +14 -1
- pymax/app.py +26 -6
- pymax/base.py +48 -3
- pymax/dispatch/__init__.py +12 -1
- pymax/dispatch/dispatcher.py +134 -16
- pymax/dispatch/enums.py +1 -0
- pymax/dispatch/router.py +86 -4
- pymax/infra/chat.py +21 -0
- pymax/infra/message.py +29 -6
- pymax/infra/user.py +12 -1
- pymax/protocol/tcp/compression.py +18 -0
- pymax/protocol/tcp/payload.py +20 -4
- pymax/protocol/tcp/protocol.py +5 -1
- pymax/session/store.py +11 -0
- pymax/types/domain/__init__.py +1 -1
- pymax/types/domain/chat.py +21 -1
- pymax/types/domain/message.py +32 -9
- pymax/types/domain/user.py +14 -4
- {maxapi_python-2.2.0.dist-info → maxapi_python-2.3.1.dist-info}/WHEEL +0 -0
- {maxapi_python-2.2.0.dist-info → maxapi_python-2.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maxapi-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Python wrapper для API мессенджера Max
|
|
5
5
|
Project-URL: Homepage, https://github.com/MaxApiTeam/PyMax
|
|
6
6
|
Project-URL: Repository, https://github.com/MaxApiTeam/PyMax
|
|
@@ -31,6 +31,7 @@ Requires-Dist: pydantic>=2.10.0
|
|
|
31
31
|
Requires-Dist: python-socks[asyncio]>=2.8.1
|
|
32
32
|
Requires-Dist: qrcode>=8.2
|
|
33
33
|
Requires-Dist: websockets>=16.0
|
|
34
|
+
Requires-Dist: zstandard>=0.25.0
|
|
34
35
|
Description-Content-Type: text/markdown
|
|
35
36
|
|
|
36
37
|
# PyMax
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
pymax/__init__.py,sha256=
|
|
2
|
-
pymax/app.py,sha256=
|
|
3
|
-
pymax/base.py,sha256
|
|
1
|
+
pymax/__init__.py,sha256=y6_pIn47iHEIkP4F4y7EL-qiIpAgJqs_jVKnOmooniY,1500
|
|
2
|
+
pymax/app.py,sha256=QQldPh1YYpFeKBOMfCQZA0BFW5bUQQO7hFBYtwpioZQ,10847
|
|
3
|
+
pymax/base.py,sha256=--6VwKUvqlocFAUbPWn_CfXQ-8CYfyCigXQ70ipXOgI,10500
|
|
4
4
|
pymax/client.py,sha256=ePDxR6wEeHJbk23wI_PkOVFQuyy5tQyckV-OmKEwz5U,3985
|
|
5
5
|
pymax/client_web.py,sha256=VASqNmMvAfdpIaR6cpdtjv3UN4GoqWkiDOXdn7QmZQ0,3025
|
|
6
6
|
pymax/config.py,sha256=1dttAfxjcuWVrr939iYLDowQknvl_eOhNpJP8ugnWTg,10047
|
|
@@ -23,12 +23,12 @@ pymax/api/bots/payloads.py,sha256=aXriolCN_YvBvDZO_MPmD-fkrIVJIpVDXkOkOZVes_c,16
|
|
|
23
23
|
pymax/api/bots/service.py,sha256=ziqVlQUMFHjQydMDFyWcQMIATboQTvIdGtJhf-AK9tA,847
|
|
24
24
|
pymax/api/chats/__init__.py,sha256=UMp3uKdB3tZlnWckndgF3jaU-3-49OJE18z2d0OlmIU,155
|
|
25
25
|
pymax/api/chats/enums.py,sha256=FdByRiHYX9Xs7RyNabKqxGugNs9tJ3VBXwTb_43XGC8,627
|
|
26
|
-
pymax/api/chats/payloads.py,sha256=
|
|
27
|
-
pymax/api/chats/service.py,sha256=
|
|
26
|
+
pymax/api/chats/payloads.py,sha256=qA46Q984FgHaXenPmvtotm7ZJOKM07aiP7dBYF_0gqQ,2930
|
|
27
|
+
pymax/api/chats/service.py,sha256=jNQUlSdILR5afeTYFQmQ6mvx-rgQlWjhzmOWUA0vDcg,12064
|
|
28
28
|
pymax/api/messages/__init__.py,sha256=fw_uF6tgSbkxvqaSVE65wU0KWsu47u1CtR0Y4BbD-K0,36
|
|
29
29
|
pymax/api/messages/enums.py,sha256=tp5S2Q97a-FILcqpRhQiIRKdj0P_KSEBiCqWmb05dHs,368
|
|
30
|
-
pymax/api/messages/payloads.py,sha256=
|
|
31
|
-
pymax/api/messages/service.py,sha256=
|
|
30
|
+
pymax/api/messages/payloads.py,sha256=MQSaZD0kYbkojWNK6BJbwoS02ZjDX7eutFnJX3_L6_g,2712
|
|
31
|
+
pymax/api/messages/service.py,sha256=lGnciRoP9PkpgBDpkHQU1ukxNTwMJuGjHhM1EeSHWXU,13334
|
|
32
32
|
pymax/api/self/__init__.py,sha256=TfbqL4xLb5IMhbW8mlAK-AwVFqqPWMADogKZeWtkw0A,79
|
|
33
33
|
pymax/api/self/enums.py,sha256=iKEqPy44LyQKvAPfyhpkSXgmWWohxry73F0CVgtekwY,180
|
|
34
34
|
pymax/api/self/payloads.py,sha256=-SFqkNxC5ZLKnLty_Gyz_b_P3X1esu_7Urb4HjrQxM4,774
|
|
@@ -43,8 +43,8 @@ pymax/api/uploads/payloads.py,sha256=OqsfFJIRe2io7mltHPbDPORT8U5t27H6pvqrdNKPRHY
|
|
|
43
43
|
pymax/api/uploads/service.py,sha256=83VrmMGZG4QPnKfSdVrWoIOvER4CvegrbZ2ddxmGsq8,18551
|
|
44
44
|
pymax/api/users/__init__.py,sha256=CDakgSKwVAkX-kNoDmIo2JsFPsS7Nx3j2vIMwQnPRck,82
|
|
45
45
|
pymax/api/users/enums.py,sha256=m3d225sjWkBIRF5NNi3ChnyZ6BgKUEbPYoYwVjpifZ4,205
|
|
46
|
-
pymax/api/users/payloads.py,sha256=
|
|
47
|
-
pymax/api/users/service.py,sha256=
|
|
46
|
+
pymax/api/users/payloads.py,sha256=mWrbhu3KzrsxmBdRQU3bG8W46vtFq_Kdfb2YA7_QkNI,876
|
|
47
|
+
pymax/api/users/service.py,sha256=ndYv60kKkqZPnFOgvfau2LAjgOf-NsLQ7P4xxoWBAr4,4663
|
|
48
48
|
pymax/auth/__init__.py,sha256=KgghKg0CPcJzX_D14TvxuDzhNmoVoFGjv414NouJVgk,512
|
|
49
49
|
pymax/auth/base.py,sha256=v492FMUmSKft6oJRmCdIr33sy50Z0kqBwu5t7cVLQe4,1222
|
|
50
50
|
pymax/auth/email.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -60,12 +60,12 @@ pymax/connection/readers/__init__.py,sha256=9g5ZbcWiHxW1kAi2ckiNqejoGHwoDwFd4Wvt
|
|
|
60
60
|
pymax/connection/readers/base.py,sha256=FlvyMUFoZ4FORAh0vBU6_I0BnrQ9_1twpUIKz-Vkj58,126
|
|
61
61
|
pymax/connection/readers/tcp.py,sha256=62nqlllE0gjQQQYVfjBt0arAJ8tEHH_SMZOOjtXNYdw,1073
|
|
62
62
|
pymax/connection/readers/ws.py,sha256=IjeH5CoXvBWde46tZLx6uKONkjhCU6NeC79x3Swfw8E,368
|
|
63
|
-
pymax/dispatch/__init__.py,sha256=
|
|
64
|
-
pymax/dispatch/dispatcher.py,sha256=
|
|
65
|
-
pymax/dispatch/enums.py,sha256=
|
|
63
|
+
pymax/dispatch/__init__.py,sha256=Big4ZqRSP4fzNAKc5mISkqQbWcYhjYMfg7UtHHCGZJw,376
|
|
64
|
+
pymax/dispatch/dispatcher.py,sha256=xk_8ZbJRoRUOTPg5idlZPCAAZ_kOBbrrcT2LuI57SdE,11782
|
|
65
|
+
pymax/dispatch/enums.py,sha256=LxX6tCxkB7mNnHr3AydKX-s-iZ8_bjoMl8IxhC7gdhQ,446
|
|
66
66
|
pymax/dispatch/mapping.py,sha256=wO9BPl3meCWE7AA0XdC0iGt-4zuGVozcCqnleCTFdI0,3303
|
|
67
67
|
pymax/dispatch/resolvers.py,sha256=FZvjYxQwy5CnTl6QwdfRRodK9cGrAn6MuXTZqcsJkPE,1987
|
|
68
|
-
pymax/dispatch/router.py,sha256=
|
|
68
|
+
pymax/dispatch/router.py,sha256=0dwXCR7TgAGNiq9oryXIuyAsJ15DJ3OMt9hrRenUBio,10453
|
|
69
69
|
pymax/files/__init__.py,sha256=TR0N6YXX4IHBsXQo8uiK68C-Xg3LSj48d4Zsv71CWZE,126
|
|
70
70
|
pymax/files/base.py,sha256=0aksCGxmrvqEKJdhRWcuPZJfEn7RWcl5tn5gn63q1os,2689
|
|
71
71
|
pymax/files/file.py,sha256=JSkuKx0fdGxzcsyMajWTosFzTlfgZYvQbh-RDp9OZyQ,2337
|
|
@@ -78,26 +78,26 @@ pymax/infra/__init__.py,sha256=lKq6cehhB87HltV53TD0kr0p7ekWlZlrKTpaYiq0XFw,28
|
|
|
78
78
|
pymax/infra/auth.py,sha256=YuYH_NWNz8UfPyko6bv_cIzXDIvFeivDrxZCIpGoANU,3648
|
|
79
79
|
pymax/infra/base.py,sha256=sOo40Qkp14bE6CxVKppJhy3e0F5VhFPhnL3iBs8y5dA,368
|
|
80
80
|
pymax/infra/bots.py,sha256=IJs4ErFxjbMeiHfQgpDiDhtEu9ws-lIp8fb8I96szy8,1202
|
|
81
|
-
pymax/infra/chat.py,sha256=
|
|
82
|
-
pymax/infra/message.py,sha256=
|
|
81
|
+
pymax/infra/chat.py,sha256=Jb5kzUnHtCg98glS4Jf-WEHIzZg4oIcS-MA8aGeJuKk,13884
|
|
82
|
+
pymax/infra/message.py,sha256=4AUp8EUQ46GhsJMgh7YmgogkBT0K4wUqEkIfrFT--oI,11737
|
|
83
83
|
pymax/infra/protocol.py,sha256=I2WrAeAgATuNSdK2gvHU-MhhxdRBfSMGMovF5HPFsQw,208
|
|
84
84
|
pymax/infra/self.py,sha256=h2GnzcCF2-FeY9-qBjtHjrSCx7elc4RjuCXPaewDkgg,4828
|
|
85
|
-
pymax/infra/user.py,sha256=
|
|
85
|
+
pymax/infra/user.py,sha256=wy6lElmJx7Ypjkk48kDrlLiL3V3p7pzO2MevsgCNy1k,4801
|
|
86
86
|
pymax/protocol/__init__.py,sha256=rDn1Twi1iBDUk1155YI9jyeQyaGVDRuPoWdlaaLZnsw,242
|
|
87
87
|
pymax/protocol/base.py,sha256=_bisk1BU_GSMSPIqfnizSCgm_qBrdbB5cJw6foAa2tc,294
|
|
88
88
|
pymax/protocol/enums.py,sha256=9y4kn9y2pKinaT_twdeW5MXVH3O-sJF6h0v5KbC9VkA,4376
|
|
89
89
|
pymax/protocol/models.py,sha256=kno-09OoPoLKBMixS9nrDkCbxcIwvU37RlZgaq5MfVo,584
|
|
90
90
|
pymax/protocol/tcp/__init__.py,sha256=ivIZZ-UoT_MiRIUWTLiyeKSBOzf9XztSprHPHgoWNuI,34
|
|
91
|
-
pymax/protocol/tcp/compression.py,sha256=
|
|
91
|
+
pymax/protocol/tcp/compression.py,sha256=JsW84MOjtUCJPxMfsfc4CX2bMH0D5sb_2mut189KHmU,3545
|
|
92
92
|
pymax/protocol/tcp/framing.py,sha256=qH2zNsJLfg5J2wNTcp1hPqsrWhZHajK-Ja7pdB_Gji0,1630
|
|
93
|
-
pymax/protocol/tcp/payload.py,sha256=
|
|
94
|
-
pymax/protocol/tcp/protocol.py,sha256
|
|
93
|
+
pymax/protocol/tcp/payload.py,sha256=roViz6uNrJvmEBukPwBPk462uFJMya48jEnLthZs-yU,4329
|
|
94
|
+
pymax/protocol/tcp/protocol.py,sha256=b5AAYQUOhq985Wc1pCPzt84pjGnVGfGxbdwRN0R91xk,2380
|
|
95
95
|
pymax/protocol/ws/__init__.py,sha256=oQK0h28B6gGItf7eHanWy_XsgUA_L9Lclnuz9ZQL9YI,33
|
|
96
96
|
pymax/protocol/ws/protocol.py,sha256=z6zzBQTlJfzwOmLsJYoYQ2L1AYpY5wYMJyPir_zfNBQ,913
|
|
97
97
|
pymax/session/__init__.py,sha256=natjnLjPjTvkfvSYtoiPxzmU44XJi0MpjWiOPD-On4g,100
|
|
98
98
|
pymax/session/models.py,sha256=jvnTHfjCX1p5TcANfWZ0vUJlciuqumatwdrgYiKm4kY,250
|
|
99
99
|
pymax/session/protocol.py,sha256=8ibp-HiaJaGmx23v7GeL0GFsvLPZZqWOuIOJnanqoCM,612
|
|
100
|
-
pymax/session/store.py,sha256=
|
|
100
|
+
pymax/session/store.py,sha256=xzUpLuS8OpcYNB1lNvUwzOJIcsGz516_aOXBh2jIWT4,8087
|
|
101
101
|
pymax/telemetry/__init__.py,sha256=6mP5y-iTbVIlqpSWIKWCTz2fFNBwkAEYwS4PFbyv9xM,71
|
|
102
102
|
pymax/telemetry/navigation.py,sha256=vlP7d863s9aIBpt9SPpjEbTXLoq3uqpuhD-VWj0w9hI,5441
|
|
103
103
|
pymax/telemetry/payloads.py,sha256=MCKYGpQ4Tvx_8LH86Et0yevea2Th2z3b7a9pfmrQRys,3959
|
|
@@ -107,24 +107,24 @@ pymax/transport/base.py,sha256=0D3srQ36F0s23pIjqd6YzRGKK9wCmXKr3wfe2XYYlos,448
|
|
|
107
107
|
pymax/transport/tcp.py,sha256=Nl-voifBze4TGB25xjf93XxU77zvFL8rsPCjBpXYd_4,3083
|
|
108
108
|
pymax/transport/websocket.py,sha256=0ezkSoENWKV00EATIizGx16-ihCnaTC76zzGnwbS_3c,1504
|
|
109
109
|
pymax/types/__init__.py,sha256=y77mfDRgxmfcamobi0rj2d3PsxhH0zxyvKPhL2iUdS8,44
|
|
110
|
-
pymax/types/domain/__init__.py,sha256=
|
|
110
|
+
pymax/types/domain/__init__.py,sha256=tdC1Vs4lH-oqDrSXS_Oc5JlnJC7RTJ2I3B63EVfiLiI,483
|
|
111
111
|
pymax/types/domain/auth.py,sha256=RmEXFS7TAgtFhnfXaiJH5XDb__2MUVgQ_Noavo9hFRY,6019
|
|
112
112
|
pymax/types/domain/base.py,sha256=Q32PcKR3gtrISySowR3zPyl9KeQCoUL6lfyTR3ulMzo,623
|
|
113
113
|
pymax/types/domain/bots.py,sha256=dErZZbvspznU6yUupzCs4R55pEWgoeps7NEhZFrb-Uo,333
|
|
114
|
-
pymax/types/domain/chat.py,sha256=
|
|
114
|
+
pymax/types/domain/chat.py,sha256=mkee157TESCqxC_1NXveHLFLxtLOT7up22IvBnN__2o,20547
|
|
115
115
|
pymax/types/domain/element.py,sha256=Hks0MulMcPAhbpuUmJa5c2d9Emwy3garh1k6bd4QHKA,655
|
|
116
116
|
pymax/types/domain/enums.py,sha256=eGMWnlv0mYJ8K-0FsC_wp4Co2M6YWyKI1Lx90MJ6s4g,412
|
|
117
117
|
pymax/types/domain/error.py,sha256=WZoDAXf5vt8O4VYy7iEHLGBfH6hZQZ1fVkTz9rVCTN0,584
|
|
118
118
|
pymax/types/domain/folder.py,sha256=Z9pXMzOTfxuhUfrJpLjl5LPnVXqqFsBVfKBQm4IbGWM,2225
|
|
119
119
|
pymax/types/domain/login.py,sha256=FBKpRhNPv6Zdjo6Wfl-lK8JpzpMx7zh84f1qrq6Z0og,1301
|
|
120
120
|
pymax/types/domain/member.py,sha256=8gUNn4fBS_S2ap45A40lRlrDXYFMoq0Kx-dh57hMWQc,490
|
|
121
|
-
pymax/types/domain/message.py,sha256=
|
|
121
|
+
pymax/types/domain/message.py,sha256=a1wdIVfRcLo30rUUQzBAyLqCGcPoc-6Uk9jcDg8oqHQ,16497
|
|
122
122
|
pymax/types/domain/name.py,sha256=qMIshIqgWkVuROxmiCRhwfJf8XtmhSBoEU6nXs_gEh0,523
|
|
123
123
|
pymax/types/domain/presence.py,sha256=lKvkK0uYwY0gTSEEkaVdI-tEpWjxLOtH7gp2dy5Uh-0,534
|
|
124
124
|
pymax/types/domain/profile.py,sha256=taN5PjlKp4EDypVhDx89vmBDpSKh-kT-ISpu1tR2cY8,471
|
|
125
125
|
pymax/types/domain/session.py,sha256=T0b0qjI65kNQOCgx3nIkaIqynD4eGKyqnscbIlPFVnQ,2002
|
|
126
126
|
pymax/types/domain/sync.py,sha256=lBDWo4ACLq2ov5XBccfnI8s_dsGEA7O4mIJVBSUpyrc,3248
|
|
127
|
-
pymax/types/domain/user.py,sha256=
|
|
127
|
+
pymax/types/domain/user.py,sha256=tpHBnatNXdajS8noMy8oox9qp8Yej9b7mkdtg7BpjQM,5514
|
|
128
128
|
pymax/types/domain/attachments/__init__.py,sha256=ZVSb7sSAwabSB8A8BWF_JbooPOMIP1r6gF_NUASgGGk,471
|
|
129
129
|
pymax/types/domain/attachments/audio.py,sha256=I_Qq1qQ9bXsEzWwxEbQbkiY8CEjSKqGqinDFYY_5DY8,1117
|
|
130
130
|
pymax/types/domain/attachments/call.py,sha256=RV-BFutymZFinzUiRdaIryqx6ubku2v6y6brc-qsHOQ,818
|
|
@@ -147,7 +147,7 @@ pymax/types/events/presence.py,sha256=77Cy352CdfIkbH6ZfLijHffF8gcMz-AZk5OCeESQeD
|
|
|
147
147
|
pymax/types/events/reaction.py,sha256=7kauScAH8O-K79xS6Q3b6Vi0KwRl4lGCKGeAMAH9sEU,672
|
|
148
148
|
pymax/types/events/typing.py,sha256=vDMgqYE0jz8dN2YfqsNaJLB8k7pMd6kaDEdR_KrLk5g,376
|
|
149
149
|
pymax/types/events/video.py,sha256=swBHYadmDS0SjLXGqVqRYsuMoVZ6MjD2aYR2fbM-AJc,104
|
|
150
|
-
maxapi_python-2.
|
|
151
|
-
maxapi_python-2.
|
|
152
|
-
maxapi_python-2.
|
|
153
|
-
maxapi_python-2.
|
|
150
|
+
maxapi_python-2.3.1.dist-info/METADATA,sha256=k5ItHuOsbXla9MpvlsqOWi7lJbV2u_T8wMotxOA2vDc,7309
|
|
151
|
+
maxapi_python-2.3.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
152
|
+
maxapi_python-2.3.1.dist-info/licenses/LICENSE,sha256=hOR249ItqMdcly1A0amqEWRNRTq4Gv5NJtmQ3A5qK4E,1070
|
|
153
|
+
maxapi_python-2.3.1.dist-info/RECORD,,
|
pymax/__init__.py
CHANGED
pymax/api/chats/payloads.py
CHANGED
|
@@ -115,3 +115,9 @@ class JoinRequestActionPayload(CamelModel):
|
|
|
115
115
|
type: str = "JOIN_REQUEST" # TODO: ENUMM!!!
|
|
116
116
|
show_history: bool | None = True
|
|
117
117
|
operation: ChatMemberOperation
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class DeleteChatPayload(CamelModel):
|
|
121
|
+
chat_id: int
|
|
122
|
+
last_event_time: int
|
|
123
|
+
for_all: bool = True
|
pymax/api/chats/service.py
CHANGED
|
@@ -23,6 +23,7 @@ from .payloads import (
|
|
|
23
23
|
CreateGroupAttach,
|
|
24
24
|
CreateGroupMessage,
|
|
25
25
|
CreateGroupPayload,
|
|
26
|
+
DeleteChatPayload,
|
|
26
27
|
FetchChatsPayload,
|
|
27
28
|
FetchJoinRequests,
|
|
28
29
|
GetChatInfoPayload,
|
|
@@ -362,3 +363,20 @@ class ChatService:
|
|
|
362
363
|
chat_id=chat_id,
|
|
363
364
|
user_ids=[user_id],
|
|
364
365
|
)
|
|
366
|
+
|
|
367
|
+
async def delete_chat(
|
|
368
|
+
self,
|
|
369
|
+
chat_id: int,
|
|
370
|
+
last_event_time: int | None = None,
|
|
371
|
+
for_all: bool = True,
|
|
372
|
+
) -> None:
|
|
373
|
+
frame = DeleteChatPayload(
|
|
374
|
+
chat_id=chat_id,
|
|
375
|
+
last_event_time=(
|
|
376
|
+
last_event_time if last_event_time is not None else int(time.time() * 1000)
|
|
377
|
+
),
|
|
378
|
+
for_all=for_all,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
await self.app.invoke(Opcode.CHAT_DELETE, frame.to_payload())
|
|
382
|
+
self._remove_cached_chat(chat_id)
|
pymax/api/messages/payloads.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any, Literal
|
|
2
2
|
|
|
3
3
|
from pydantic import Field
|
|
4
4
|
|
|
@@ -46,6 +46,26 @@ class SendMessagePayload(CamelModel):
|
|
|
46
46
|
notify: bool = False
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
class ForwardLink(CamelModel):
|
|
50
|
+
type: Literal["FORWARD"] = "FORWARD"
|
|
51
|
+
message_id: str
|
|
52
|
+
chat_id: int
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ForwardMessagePayloadMessage(CamelModel):
|
|
56
|
+
cid: int
|
|
57
|
+
link: ForwardLink
|
|
58
|
+
attaches: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload] = Field(
|
|
59
|
+
default_factory=list
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ForwardMessagePayload(CamelModel):
|
|
64
|
+
chat_id: int
|
|
65
|
+
message: ForwardMessagePayloadMessage
|
|
66
|
+
notify: bool = True
|
|
67
|
+
|
|
68
|
+
|
|
49
69
|
class ChatHistoryPayload(CamelModel):
|
|
50
70
|
chat_id: int
|
|
51
71
|
forward: int
|
pymax/api/messages/service.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
+
from collections.abc import Sequence
|
|
4
5
|
from typing import TYPE_CHECKING, TypeAlias
|
|
5
6
|
|
|
6
7
|
from pymax.api.binding import bind_api_model, bind_api_models
|
|
@@ -35,6 +36,9 @@ from .payloads import (
|
|
|
35
36
|
ChatHistoryPayload,
|
|
36
37
|
DeleteMessagePayload,
|
|
37
38
|
EditMessagePayload,
|
|
39
|
+
ForwardLink,
|
|
40
|
+
ForwardMessagePayload,
|
|
41
|
+
ForwardMessagePayloadMessage,
|
|
38
42
|
GetFilePayload,
|
|
39
43
|
GetMessagesPayload,
|
|
40
44
|
GetReactionsPayload,
|
|
@@ -52,7 +56,7 @@ if TYPE_CHECKING:
|
|
|
52
56
|
from pymax.app import App
|
|
53
57
|
|
|
54
58
|
SendAttachment: TypeAlias = Photo | File | Video
|
|
55
|
-
SendAttachments: TypeAlias =
|
|
59
|
+
SendAttachments: TypeAlias = Sequence[SendAttachment] | None
|
|
56
60
|
|
|
57
61
|
logger = get_logger(__name__)
|
|
58
62
|
|
|
@@ -138,6 +142,42 @@ class MessageService:
|
|
|
138
142
|
logger.info("message sent chat_id=%s", chat_id)
|
|
139
143
|
return message
|
|
140
144
|
|
|
145
|
+
async def forward_message(
|
|
146
|
+
self,
|
|
147
|
+
chat_id: int,
|
|
148
|
+
message_id: int | str,
|
|
149
|
+
source_chat_id: int | None = None,
|
|
150
|
+
*,
|
|
151
|
+
notify: bool = True,
|
|
152
|
+
) -> Message | None:
|
|
153
|
+
source_chat_id = chat_id if source_chat_id is None else source_chat_id
|
|
154
|
+
logger.info(
|
|
155
|
+
"forwarding message source_chat_id=%s chat_id=%s message_id=%s",
|
|
156
|
+
source_chat_id,
|
|
157
|
+
chat_id,
|
|
158
|
+
message_id,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
frame = ForwardMessagePayload(
|
|
162
|
+
chat_id=chat_id,
|
|
163
|
+
message=ForwardMessagePayloadMessage(
|
|
164
|
+
cid=-self._next_cid(),
|
|
165
|
+
link=ForwardLink(
|
|
166
|
+
message_id=str(message_id),
|
|
167
|
+
chat_id=source_chat_id,
|
|
168
|
+
),
|
|
169
|
+
),
|
|
170
|
+
notify=notify,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
response = await self.app.invoke(Opcode.MSG_SEND, frame.to_payload())
|
|
174
|
+
message = bind_api_model(
|
|
175
|
+
self.app,
|
|
176
|
+
require_payload_model(response, Message),
|
|
177
|
+
)
|
|
178
|
+
logger.info("message forwarded source_chat_id=%s chat_id=%s", source_chat_id, chat_id)
|
|
179
|
+
return message
|
|
180
|
+
|
|
141
181
|
async def get_messages(
|
|
142
182
|
self,
|
|
143
183
|
chat_id: int,
|
|
@@ -169,24 +209,15 @@ class MessageService:
|
|
|
169
209
|
chat_id: int,
|
|
170
210
|
message_id: int,
|
|
171
211
|
text: str,
|
|
172
|
-
attachment: SendAttachment | None = None,
|
|
173
212
|
attachments: SendAttachments = None,
|
|
174
213
|
) -> Message:
|
|
175
|
-
if attachment is not None and attachments:
|
|
176
|
-
logger.warning("both attachment and attachments provided; using attachments")
|
|
177
|
-
attachment = None
|
|
178
|
-
|
|
179
|
-
edit_attachments = attachments
|
|
180
|
-
if attachment is not None:
|
|
181
|
-
edit_attachments = [attachment]
|
|
182
|
-
|
|
183
214
|
clean_text, elements = Formatter.format_markdown(text)
|
|
184
215
|
frame = EditMessagePayload(
|
|
185
216
|
chat_id=chat_id,
|
|
186
217
|
message_id=message_id,
|
|
187
218
|
text=clean_text,
|
|
188
219
|
elements=elements,
|
|
189
|
-
attachments=await self._upload_attachments(
|
|
220
|
+
attachments=await self._upload_attachments(attachments),
|
|
190
221
|
)
|
|
191
222
|
|
|
192
223
|
response = await self.app.invoke(Opcode.MSG_EDIT, frame.to_payload())
|
pymax/api/users/payloads.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
|
|
1
3
|
from pymax.api.models import CamelModel
|
|
4
|
+
from pymax.types.domain import ContactInfo
|
|
2
5
|
|
|
3
6
|
from .enums import ContactAction
|
|
4
7
|
|
|
@@ -14,3 +17,22 @@ class SearchByPhonePayload(CamelModel):
|
|
|
14
17
|
class ContactActionPayload(CamelModel):
|
|
15
18
|
contact_id: int
|
|
16
19
|
action: ContactAction
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _ContactPayload(CamelModel):
|
|
23
|
+
first_name: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ImportContactsPayload(CamelModel):
|
|
27
|
+
contact_list: dict[str, _ContactPayload] # phone -> contact payload
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_contacts(cls, contacts: Iterable[ContactInfo]) -> "ImportContactsPayload":
|
|
31
|
+
return cls(
|
|
32
|
+
contact_list={
|
|
33
|
+
contact.phone: _ContactPayload(
|
|
34
|
+
first_name=contact.first_name,
|
|
35
|
+
)
|
|
36
|
+
for contact in contacts
|
|
37
|
+
}
|
|
38
|
+
)
|
pymax/api/users/service.py
CHANGED
|
@@ -10,12 +10,13 @@ from pymax.api.response import (
|
|
|
10
10
|
)
|
|
11
11
|
from pymax.logging import get_logger
|
|
12
12
|
from pymax.protocol import InboundFrame, Opcode
|
|
13
|
-
from pymax.types.domain import Session, User
|
|
13
|
+
from pymax.types.domain import ContactInfo, Session, User
|
|
14
14
|
|
|
15
15
|
from .enums import ContactAction, UserPayloadKey
|
|
16
16
|
from .payloads import (
|
|
17
17
|
ContactActionPayload,
|
|
18
18
|
FetchContactsPayload,
|
|
19
|
+
ImportContactsPayload,
|
|
19
20
|
SearchByPhonePayload,
|
|
20
21
|
)
|
|
21
22
|
|
|
@@ -122,5 +123,17 @@ class UserService:
|
|
|
122
123
|
self.app.users.pop(contact_id, None)
|
|
123
124
|
return True
|
|
124
125
|
|
|
126
|
+
async def import_contacts(self, contacts: list[ContactInfo]) -> list[User]:
|
|
127
|
+
frame = ImportContactsPayload.from_contacts(contacts)
|
|
128
|
+
|
|
129
|
+
response = await self.app.invoke(Opcode.SYNC, frame.to_payload())
|
|
130
|
+
|
|
131
|
+
users = parse_payload_list(
|
|
132
|
+
response, UserPayloadKey.CONTACTS, User
|
|
133
|
+
) # TODO: maybe also return phone mapping?
|
|
134
|
+
|
|
135
|
+
# {contacts: [...], phones: {data[0]: server_phone}}
|
|
136
|
+
return [self._cache_user(user) for user in users]
|
|
137
|
+
|
|
125
138
|
def get_chat_id(self, first_user_id: int, second_user_id: int) -> int:
|
|
126
139
|
return first_user_id ^ second_user_id
|
pymax/app.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Any, Generic, TypeVar
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
3
3
|
|
|
4
4
|
from pymax.api import ApiFacade
|
|
5
5
|
from pymax.auth import AuthFlow
|
|
6
6
|
from pymax.config import ClientConfig
|
|
7
7
|
from pymax.connection import ConnectionManager
|
|
8
8
|
from pymax.dispatch import Dispatcher
|
|
9
|
-
from pymax.dispatch.router import Router
|
|
9
|
+
from pymax.dispatch.router import EventType, Router
|
|
10
10
|
from pymax.exceptions import ApiError
|
|
11
11
|
from pymax.logging import get_logger
|
|
12
12
|
from pymax.protocol import Command, InboundFrame, OutboundFrame
|
|
@@ -17,8 +17,11 @@ from pymax.telemetry import TelemetryService
|
|
|
17
17
|
from pymax.types import MaxApiError, Message
|
|
18
18
|
from pymax.types.domain import Chat, Profile, User
|
|
19
19
|
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from pymax.base import BaseClient
|
|
22
|
+
|
|
20
23
|
logger = get_logger(__name__)
|
|
21
|
-
ClientT = TypeVar("ClientT")
|
|
24
|
+
ClientT = TypeVar("ClientT", bound="BaseClient")
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
class App(Generic[ClientT]):
|
|
@@ -124,9 +127,26 @@ class App(Generic[ClientT]):
|
|
|
124
127
|
self.session = session_data
|
|
125
128
|
|
|
126
129
|
logger.debug("logging in")
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
response = await self.api.auth.login(
|
|
133
|
+
self.config.device.user_agent,
|
|
134
|
+
)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
handled = False
|
|
137
|
+
if self.dispatcher.client is not None:
|
|
138
|
+
handled = await self.dispatcher.emit_error(
|
|
139
|
+
e,
|
|
140
|
+
EventType.ON_START,
|
|
141
|
+
None,
|
|
142
|
+
self.dispatcher.root_router,
|
|
143
|
+
None,
|
|
144
|
+
)
|
|
145
|
+
if not handled:
|
|
146
|
+
raise
|
|
147
|
+
|
|
148
|
+
await self.close()
|
|
149
|
+
return
|
|
130
150
|
|
|
131
151
|
if response.token is not None and response.token != self.session.token:
|
|
132
152
|
await self.store.update_token(self.session.token, response.token)
|
pymax/base.py
CHANGED
|
@@ -5,7 +5,8 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
|
-
from pymax.dispatch import Router
|
|
8
|
+
from pymax.dispatch import ErrorScope, Router
|
|
9
|
+
from pymax.dispatch.router import DisconnectDecorator, ErrorDecorator
|
|
9
10
|
from pymax.infra import BaseMixin
|
|
10
11
|
from pymax.logging import get_logger
|
|
11
12
|
|
|
@@ -128,6 +129,10 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
128
129
|
while True:
|
|
129
130
|
try:
|
|
130
131
|
await self._app.start()
|
|
132
|
+
if not self._app.started:
|
|
133
|
+
await self.close()
|
|
134
|
+
return
|
|
135
|
+
|
|
131
136
|
await self._app.dispatcher.emit_start(self)
|
|
132
137
|
await self._connection.wait_closed()
|
|
133
138
|
except asyncio.CancelledError:
|
|
@@ -138,14 +143,21 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
138
143
|
EOFError,
|
|
139
144
|
OSError,
|
|
140
145
|
TimeoutError,
|
|
141
|
-
):
|
|
146
|
+
) as e:
|
|
142
147
|
await self.close()
|
|
148
|
+
await self._app.dispatcher.emit_disconnect(
|
|
149
|
+
e,
|
|
150
|
+
self.extra_config.reconnect,
|
|
151
|
+
self.extra_config.reconnect_delay,
|
|
152
|
+
)
|
|
153
|
+
|
|
143
154
|
if not self.extra_config.reconnect:
|
|
144
155
|
raise
|
|
145
156
|
|
|
146
|
-
logger.
|
|
157
|
+
logger.debug(
|
|
147
158
|
"client connection failed; reconnecting in %s seconds",
|
|
148
159
|
self.extra_config.reconnect_delay,
|
|
160
|
+
exc_info=True,
|
|
149
161
|
)
|
|
150
162
|
await asyncio.sleep(self.extra_config.reconnect_delay)
|
|
151
163
|
self._reset_runtime()
|
|
@@ -237,6 +249,39 @@ class BaseClient(BaseMixin, ABC, Generic[ClientT]):
|
|
|
237
249
|
"""Регистрирует обработчик исходных входящих frame-ов."""
|
|
238
250
|
return self._router.on_raw(*filters)
|
|
239
251
|
|
|
252
|
+
def on_error(self, scope: ErrorScope = ErrorScope.GLOBAL) -> ErrorDecorator[ClientT]:
|
|
253
|
+
"""Регистрирует обработчик ошибок dispatch-а и запуска клиента."""
|
|
254
|
+
return self._router.on_error(scope)
|
|
255
|
+
|
|
256
|
+
def on_disconnect(self) -> DisconnectDecorator:
|
|
257
|
+
"""Регистрирует обработчик сетевого отключения перед reconnect."""
|
|
258
|
+
return self._router.on_disconnect()
|
|
259
|
+
|
|
240
260
|
def include_router(self, router: Router[ClientT]) -> None:
|
|
241
261
|
"""Подключает дочерний router к root router клиента."""
|
|
242
262
|
self._router.include_router(router)
|
|
263
|
+
|
|
264
|
+
async def relogin(self: ClientT, drop_config_token: bool = True, start: bool = True) -> None: # noqa: PYI019
|
|
265
|
+
"""Удаляет текущую локальную сессию и запускает авторизацию заново.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
drop_config_token: Сбросить token, переданный через ``ExtraConfig``.
|
|
269
|
+
start: Сразу запустить клиента после сброса runtime.
|
|
270
|
+
"""
|
|
271
|
+
store = self._app.store
|
|
272
|
+
session = self._app.session
|
|
273
|
+
|
|
274
|
+
if session is None:
|
|
275
|
+
raise RuntimeError("Cannot relogin before session is loaded")
|
|
276
|
+
|
|
277
|
+
await store.delete_session(session.token)
|
|
278
|
+
await self.close()
|
|
279
|
+
|
|
280
|
+
if drop_config_token:
|
|
281
|
+
self.extra_config.token = None
|
|
282
|
+
self._config.token = None
|
|
283
|
+
|
|
284
|
+
self._reset_runtime()
|
|
285
|
+
|
|
286
|
+
if start:
|
|
287
|
+
await self.start()
|
pymax/dispatch/__init__.py
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
from .dispatcher import Dispatcher
|
|
2
2
|
from .enums import EventType
|
|
3
|
-
from .router import
|
|
3
|
+
from .router import (
|
|
4
|
+
ClientRouter,
|
|
5
|
+
DisconnectCallback,
|
|
6
|
+
DisconnectDecorator,
|
|
7
|
+
ErrorContext,
|
|
8
|
+
ErrorScope,
|
|
9
|
+
Router,
|
|
10
|
+
)
|
|
4
11
|
|
|
5
12
|
__all__ = (
|
|
6
13
|
"ClientRouter",
|
|
14
|
+
"DisconnectCallback",
|
|
15
|
+
"DisconnectDecorator",
|
|
7
16
|
"Dispatcher",
|
|
17
|
+
"ErrorContext",
|
|
18
|
+
"ErrorScope",
|
|
8
19
|
"EventType",
|
|
9
20
|
"Router",
|
|
10
21
|
)
|