1
1
import abc
2
2
import base64
3
+ import dataclasses
3
4
import logging
4
5
import sys
5
6
import typing as t
6
7
7
8
from . import ahttp , domain , http
8
9
from .constants import DEFAULT_URL , VERSION
10
+ from .encryption import BaseEncryptor
9
11
10
12
logger = logging .getLogger (__name__ )
11
13
12
14
13
15
class BaseClient (abc .ABC ):
14
16
def __init__ (
15
- self , login : str , password : str , * , base_url : str = DEFAULT_URL
17
+ self ,
18
+ login : str ,
19
+ password : str ,
20
+ * ,
21
+ base_url : str = DEFAULT_URL ,
22
+ encryptor : t .Optional [BaseEncryptor ] = None ,
16
23
) -> None :
17
24
credentials = base64 .b64encode (f"{ login } :{ password } " .encode ("utf-8" )).decode (
18
25
"utf-8"
@@ -23,6 +30,44 @@ def __init__(
23
30
"User-Agent" : f"android-sms-gateway/{ VERSION } (client; python { sys .version_info .major } .{ sys .version_info .minor } )" ,
24
31
}
25
32
self .base_url = base_url .rstrip ("/" )
33
+ self .encryptor = encryptor
34
+
35
+ def _encrypt (self , message : domain .Message ) -> domain .Message :
36
+ if self .encryptor is None :
37
+ return message
38
+
39
+ if message .is_encrypted :
40
+ raise ValueError ("Message is already encrypted" )
41
+
42
+ message = dataclasses .replace (
43
+ message ,
44
+ is_encrypted = True ,
45
+ message = self .encryptor .encrypt (message .message ),
46
+ phone_numbers = [
47
+ self .encryptor .encrypt (phone ) for phone in message .phone_numbers
48
+ ],
49
+ )
50
+
51
+ return message
52
+
53
+ def _decrypt (self , state : domain .MessageState ) -> domain .MessageState :
54
+ if state .is_encrypted and self .encryptor is None :
55
+ raise ValueError ("Message is encrypted but encryptor is not set" )
56
+
57
+ if self .encryptor is None :
58
+ return state
59
+
60
+ return dataclasses .replace (
61
+ state ,
62
+ recipients = [
63
+ dataclasses .replace (
64
+ recipient ,
65
+ phone_number = self .encryptor .decrypt (recipient .phone_number ),
66
+ )
67
+ for recipient in state .recipients
68
+ ],
69
+ is_encrypted = False ,
70
+ )
26
71
27
72
28
73
class APIClient (BaseClient ):
@@ -32,10 +77,11 @@ def __init__(
32
77
password : str ,
33
78
* ,
34
79
base_url : str = DEFAULT_URL ,
35
- http_client : t .Optional [http .HttpClient ] = None ,
80
+ encryptor : t .Optional [BaseEncryptor ] = None ,
81
+ http : t .Optional [http .HttpClient ] = None ,
36
82
) -> None :
37
- super ().__init__ (login , password , base_url = base_url )
38
- self .http = http_client
83
+ super ().__init__ (login , password , base_url = base_url , encryptor = encryptor )
84
+ self .http = http
39
85
40
86
def __enter__ (self ):
41
87
if self .http is not None :
@@ -50,17 +96,22 @@ def __exit__(self, exc_type, exc_val, exc_tb):
50
96
self .http = None
51
97
52
98
def send (self , message : domain .Message ) -> domain .MessageState :
53
- return domain .MessageState .from_dict (
54
- self .http .post (
55
- f"{ self .base_url } /message" ,
56
- payload = message .asdict (),
57
- headers = self .headers ,
99
+ message = self ._encrypt (message )
100
+ return self ._decrypt (
101
+ domain .MessageState .from_dict (
102
+ self .http .post (
103
+ f"{ self .base_url } /message" ,
104
+ payload = message .asdict (),
105
+ headers = self .headers ,
106
+ )
58
107
)
59
108
)
60
109
61
110
def get_state (self , _id : str ) -> domain .MessageState :
62
- return domain .MessageState .from_dict (
63
- self .http .get (f"{ self .base_url } /message/{ _id } " , headers = self .headers )
111
+ return self ._decrypt (
112
+ domain .MessageState .from_dict (
113
+ self .http .get (f"{ self .base_url } /message/{ _id } " , headers = self .headers )
114
+ )
64
115
)
65
116
66
117
@@ -71,9 +122,10 @@ def __init__(
71
122
password : str ,
72
123
* ,
73
124
base_url : str = DEFAULT_URL ,
125
+ encryptor : t .Optional [BaseEncryptor ] = None ,
74
126
http_client : t .Optional [ahttp .AsyncHttpClient ] = None ,
75
127
) -> None :
76
- super ().__init__ (login , password , base_url = base_url )
128
+ super ().__init__ (login , password , base_url = base_url , encryptor = encryptor )
77
129
self .http = http_client
78
130
79
131
async def __aenter__ (self ):
@@ -89,15 +141,22 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
89
141
self .http = None
90
142
91
143
async def send (self , message : domain .Message ) -> domain .MessageState :
92
- return domain .MessageState .from_dict (
93
- await self .http .post (
94
- f"{ self .base_url } /message" ,
95
- payload = message .asdict (),
96
- headers = self .headers ,
144
+ message = self ._encrypt (message )
145
+ return self ._decrypt (
146
+ domain .MessageState .from_dict (
147
+ await self .http .post (
148
+ f"{ self .base_url } /message" ,
149
+ payload = message .asdict (),
150
+ headers = self .headers ,
151
+ )
97
152
)
98
153
)
99
154
100
155
async def get_state (self , _id : str ) -> domain .MessageState :
101
- return domain .MessageState .from_dict (
102
- await self .http .get (f"{ self .base_url } /message/{ _id } " , headers = self .headers )
156
+ return self ._decrypt (
157
+ domain .MessageState .from_dict (
158
+ await self .http .get (
159
+ f"{ self .base_url } /message/{ _id } " , headers = self .headers
160
+ )
161
+ )
103
162
)
0 commit comments