python-aidot 0.1.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.
aidot/__init__.py ADDED
File without changes
aidot/aes_utils.py ADDED
@@ -0,0 +1,25 @@
1
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
2
+ from cryptography.hazmat.backends import default_backend
3
+ from cryptography.hazmat.primitives import padding
4
+
5
+ def aes_encrypt(plaintext, key):
6
+ padder = padding.PKCS7(algorithms.AES.block_size).padder()
7
+ padded_data = padder.update(plaintext) + padder.finalize()
8
+
9
+ cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
10
+ encryptor = cipher.encryptor()
11
+
12
+ ciphertext = encryptor.update(padded_data) + encryptor.finalize()
13
+
14
+ return ciphertext
15
+
16
+ def aes_decrypt(ciphertext, key):
17
+ cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
18
+ decryptor = cipher.decryptor()
19
+
20
+ decrypted_data = decryptor.update(ciphertext) + decryptor.finalize()
21
+
22
+ unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
23
+ plaintext = unpadder.update(decrypted_data) + unpadder.finalize()
24
+
25
+ return plaintext.decode()
aidot/discover.py ADDED
@@ -0,0 +1,74 @@
1
+ import socket
2
+ import binascii
3
+ import threading
4
+ import json
5
+ import time
6
+ import logging
7
+
8
+ from .aes_utils import aes_encrypt,aes_decrypt
9
+ import asyncio
10
+
11
+ _LOGGER = logging.getLogger(__name__)
12
+
13
+ class BroadcastProtocol:
14
+
15
+ def __init__(self,callback,user_id):
16
+ self.aes_key = bytearray(32)
17
+ key_string = "T54uednca587"
18
+ key_bytes = key_string.encode()
19
+ self.aes_key[:len(key_bytes)] = key_bytes
20
+
21
+ self._discoverCb = callback
22
+ self.user_id = user_id
23
+
24
+ def connection_made(self, transport):
25
+ self.transport = transport
26
+ sock = transport.get_extra_info("socket")
27
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
28
+ self.discover_task = asyncio.create_task(self.do_discover())
29
+
30
+ async def do_discover(self):
31
+ while True:
32
+ current_timestamp_milliseconds = int(time.time() * 1000)
33
+ seq = str(current_timestamp_milliseconds + 1)[-9:]
34
+ message = {
35
+ "protocolVer":"2.0.0",
36
+ "service":"device",
37
+ "method":"devDiscoveryReq",
38
+ "seq": seq,
39
+ "srcAddr":f"0.{self.user_id}]",
40
+ "tst":current_timestamp_milliseconds,
41
+ "payload":{
42
+ "extends":{ },
43
+ "localCtrFlag":1,
44
+ "timestamp":str(current_timestamp_milliseconds)
45
+ }
46
+ }
47
+ send_data = aes_encrypt(json.dumps(message).encode(),self.aes_key)
48
+ self.transport.sendto(send_data, ('255.255.255.255', 6666))
49
+ await asyncio.sleep(3)
50
+
51
+ def datagram_received(self, data, addr):
52
+ data_str = aes_decrypt(data,self.aes_key)
53
+ data_json = json.loads(data_str)
54
+ if("payload" in data_json):
55
+ if("mac" in data_json["payload"]):
56
+ devId = data_json["payload"]["devId"]
57
+ if self._discoverCb:
58
+ self._discoverCb(devId,{"ipAddress" : addr[0]})
59
+
60
+ def error_received(self, exc):
61
+ _LOGGER.error(f"Error occurred: {exc}")
62
+
63
+ class Discover:
64
+
65
+ async def broadcast_message(self,callback,user_id):
66
+ transport, protocol = await asyncio.get_event_loop().create_datagram_endpoint(
67
+ lambda: BroadcastProtocol(callback,user_id),
68
+ local_addr=("0.0.0.0", 0),
69
+ )
70
+
71
+
72
+
73
+
74
+
aidot/lan.py ADDED
@@ -0,0 +1,292 @@
1
+ import socket
2
+ import struct
3
+ import binascii
4
+ import time
5
+ from datetime import datetime
6
+ import json
7
+ import threading
8
+ import colorsys
9
+ from time import sleep
10
+ import asyncio
11
+ import logging
12
+
13
+ from .aes_utils import aes_encrypt,aes_decrypt
14
+ _LOGGER = logging.getLogger(__name__)
15
+
16
+ class Lan(object):
17
+
18
+ _is_on : bool = False
19
+ _dimming = 0
20
+ _rgdb : int
21
+ _cct : int
22
+ _login_uuid = 0
23
+ _available : bool = False
24
+
25
+ _connectAndLogin : bool = False
26
+ _connecting = False
27
+ _simpleVersion = ""
28
+ _colorMode = ""
29
+
30
+ def __init__(self,device:dict,user_info:dict) -> None:
31
+ self.ping_count = 0
32
+
33
+ if "id" in user_info:
34
+ self.user_id = user_info["id"]
35
+
36
+ if "aesKey" in device :
37
+ key_string = device["aesKey"][0]
38
+ if key_string is not None:
39
+ self.aes_key = bytearray(16)
40
+ key_bytes = key_string.encode()
41
+ self.aes_key[:len(key_bytes)] = key_bytes
42
+
43
+ if "password" in device:
44
+ self.password = device["password"]
45
+
46
+ if "id" in device:
47
+ self.device_id = device["id"]
48
+
49
+ if "simpleVersion" in device:
50
+ self._simpleVersion = device["simpleVersion"]
51
+
52
+ async def connect(self,ipAddress):
53
+ self.reader = self.writer = None
54
+ self._connecting = True
55
+ try:
56
+ self.reader, self.writer = await asyncio.open_connection(ipAddress, 10000)
57
+ sock: socket.socket = self.writer.get_extra_info("socket")
58
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
59
+ self.seq_num = 1
60
+ await self.login()
61
+ self._connectAndLogin = True
62
+ except Exception as e:
63
+ self._connectAndLogin = False
64
+ finally:
65
+ self._connecting = False
66
+
67
+ def setUpdateDeviceCb(self,callback):
68
+ self._updateDeviceCb = callback
69
+
70
+ @property
71
+ def brightness(self) -> int:
72
+ return self._dimming * 255 / 100
73
+
74
+ def printfHex(self,packet):
75
+ hex_representation = binascii.hexlify(packet).decode()
76
+
77
+ def getSendPacket(self,message,msgtype):
78
+ magic = struct.pack('>H', 0x1eed)
79
+ _msgtype = struct.pack('>h', msgtype)
80
+
81
+ if self.aes_key is not None:
82
+ send_data = aes_encrypt(message,self.aes_key)
83
+ else :
84
+ send_data = message
85
+
86
+ bodysize = struct.pack('>i', len(send_data))
87
+ packet = magic + _msgtype + bodysize + send_data
88
+
89
+ return packet
90
+
91
+ async def login(self):
92
+ login_seq = str(int(time.time() * 1000) + self._login_uuid)[-9:]
93
+ self._login_uuid += 1
94
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
95
+ message = {
96
+ "service":"device",
97
+ "method":"loginReq",
98
+ "seq":login_seq,
99
+ "srcAddr":self.user_id,
100
+ "deviceId":self.device_id,
101
+ "payload":{
102
+ "userId":self.user_id,
103
+ "password":self.password,
104
+ "timestamp":timestamp,
105
+ "ascNumber":1
106
+ }
107
+ }
108
+ self.writer.write(self.getSendPacket(json.dumps(message).encode(),1))
109
+ await self.writer.drain()
110
+
111
+ data = await self.reader.read(1024)
112
+ data_len = len(data)
113
+ if(data_len <= 0):
114
+ return
115
+
116
+ magic, msgtype, bodysize = struct.unpack('>HHI', data[:8])
117
+ encrypted_data = data[8:]
118
+ if self.aes_key is not None:
119
+ decrypted_data = aes_decrypt(encrypted_data, self.aes_key)
120
+ else :
121
+ decrypted_data = encrypted_data
122
+
123
+ json_data = json.loads(decrypted_data)
124
+
125
+ self.ascNumber = json_data["payload"]["ascNumber"]
126
+ self.ascNumber += 1
127
+
128
+ self._available = True
129
+
130
+ await self.sendAction({},"getDevAttrReq")
131
+
132
+ async def recvData(self):
133
+ while True:
134
+ try :
135
+ data = await self.reader.read(1024)
136
+ except Exception as e:
137
+ _LOGGER.error(f"recv data error {e}")
138
+ await asyncio.sleep(3)
139
+ continue
140
+ data_len = len(data)
141
+ if(data_len <= 0):
142
+ break
143
+
144
+ try:
145
+ magic, msgtype, bodysize = struct.unpack('>HHI', data[:8])
146
+ encrypted_data = data[8:]
147
+ decrypted_data = aes_decrypt(encrypted_data, self.aes_key)
148
+
149
+ json_data = json.loads(decrypted_data)
150
+ except Exception as e:
151
+ _LOGGER.error(f"recv json error : {e}")
152
+ await asyncio.sleep(3)
153
+ continue
154
+
155
+ if "service" in json_data:
156
+ if "test" == json_data["service"]:
157
+ self.ping_count = 0
158
+
159
+ if "payload" in json_data:
160
+ if "ascNumber" in json_data["payload"]:
161
+ self.ascNumber = json_data["payload"]["ascNumber"]
162
+ if "attr" in json_data["payload"]:
163
+ if "OnOff" in json_data["payload"]["attr"]:
164
+ self._is_on = json_data["payload"]["attr"]["OnOff"]
165
+ if "Dimming" in json_data["payload"]["attr"]:
166
+ self._dimming = json_data["payload"]["attr"]["Dimming"]
167
+ if "RGBW" in json_data["payload"]["attr"]:
168
+ self._rgdb = json_data["payload"]["attr"]["RGBW"]
169
+ self._colorMode = "rgbw"
170
+ if "CCT" in json_data["payload"]["attr"]:
171
+ self._cct = json_data["payload"]["attr"]["CCT"]
172
+ self._colorMode = "cct"
173
+ if self._updateDeviceCb:
174
+ await self._updateDeviceCb()
175
+
176
+ async def ping_task(self):
177
+ while True:
178
+ if await self.sendPingAction() == -1 :
179
+ return
180
+ await asyncio.sleep(10)
181
+
182
+ def getOnOffAction(self,OnOff):
183
+ self._is_on = OnOff
184
+ return {"OnOff": self._is_on}
185
+
186
+ def getDimingAction(self,brightness):
187
+ self._dimming = int(brightness * 100 / 255)
188
+ return {"Dimming": self._dimming}
189
+
190
+ def getCCTAction(self,cct):
191
+ self._cct = cct
192
+ self._colorMode = "cct"
193
+ return {"CCT": self._cct}
194
+
195
+ def getRGBWAction(self,rgbw):
196
+ self._rgdb = rgbw
197
+ self._colorMode = "rgbw"
198
+ return {"RGBW": rgbw}
199
+
200
+ async def sendDevAttr(self,devAttr):
201
+ await self.sendAction(devAttr,"setDevAttrReq")
202
+
203
+ async def sendAction(self,attr,method):
204
+
205
+ current_timestamp_milliseconds = int(time.time() * 1000)
206
+
207
+ self.seq_num += 1
208
+
209
+ seq = "ha93" + str(self.seq_num).zfill(5)
210
+
211
+ if not self._is_on and not "OnOff" in attr:
212
+ attr["OnOff"] = 1
213
+ self._is_on = 1
214
+
215
+ if self._simpleVersion is not None:
216
+ action = {
217
+ "method": method,
218
+ "service": "device",
219
+ "clientId": "ha-" + self.user_id,
220
+ "srcAddr": "0." + self.user_id,
221
+ "seq": "" + seq,
222
+ "payload": {
223
+ "devId": self.device_id,
224
+ "parentId": self.device_id,
225
+ "userId": self.user_id,
226
+ "password": self.password,
227
+ "attr": attr,
228
+ "channel":"tcp",
229
+ "ascNumber":self.ascNumber,
230
+ },
231
+ "tst": current_timestamp_milliseconds,
232
+ # "tid": "homeassistant",
233
+ "deviceId": self.device_id,
234
+ }
235
+ else :
236
+ action = {
237
+ "method": method,
238
+ "service": "device",
239
+ "seq": "" + seq,
240
+ "srcAddr": "0." + self.user_id,
241
+ "payload": {
242
+ "attr": attr,
243
+ "ascNumber":self.ascNumber,
244
+ },
245
+ "tst": current_timestamp_milliseconds,
246
+ # "tid": "homeassistant",
247
+ "deviceId": self.device_id,
248
+ }
249
+
250
+ try:
251
+ self.writer.write(self.getSendPacket(json.dumps(action).encode(),1))
252
+ await self.writer.drain()
253
+ except BrokenPipeError as e :
254
+ _LOGGER.error(f"{self.device_id} send action error {e}")
255
+ except Exception as e:
256
+ _LOGGER.error(f"{self.device_id} send action error {e}")
257
+
258
+ async def sendPingAction(self):
259
+ ping = {
260
+ "service": "test",
261
+ "method": "pingreq",
262
+ "seq": "123456",
263
+ "srcAddr": "x.xxxxxxx",
264
+ "payload": {}
265
+ }
266
+ try:
267
+ if self.ping_count >= 2 :
268
+ _LOGGER.error(f"Last ping did not return within 20 seconds. device id:{self.device_id}")
269
+ await self.reset()
270
+ return -1
271
+ self.writer.write(self.getSendPacket(json.dumps(ping).encode(),2))
272
+ await self.writer.drain()
273
+ self.ping_count += 1
274
+ return 1
275
+ except Exception as e:
276
+ _LOGGER.error(f"{self.device_id} ping error {e}")
277
+ await self.reset()
278
+ return -1
279
+
280
+ async def reset(self):
281
+ try:
282
+ if self.writer:
283
+ self.writer.close()
284
+ await self.writer.wait_closed()
285
+ except Exception as e:
286
+ _LOGGER.error(f"{self.device_id} writer close error {e}")
287
+ self._connectAndLogin = False
288
+ self._available = False
289
+ self.ping_count = 0
290
+ if self._updateDeviceCb:
291
+ await self._updateDeviceCb()
292
+
aidot/login_const.py ADDED
@@ -0,0 +1,14 @@
1
+ """Constants for the aidot integration."""
2
+
3
+
4
+ APP_ID = "1383974540041977857"
5
+ BASE_URL = "https://prod-us-api.arnoo.com/v17"
6
+
7
+ PUBLIC_KEY_PEM = b"""
8
+ -----BEGIN PUBLIC KEY-----
9
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtQAnPCi8ksPnS1Du6z96PsKfN
10
+ p2Gp/f/bHwlrAdplbX3p7/TnGpnbJGkLq8uRxf6cw+vOthTsZjkPCF7CatRvRnTj
11
+ c9fcy7yE0oXa5TloYyXD6GkxgftBbN/movkJJGQCc7gFavuYoAdTRBOyQoXBtm0m
12
+ kXMSjXOldI/290b9BQIDAQAB
13
+ -----END PUBLIC KEY-----
14
+ """
aidot/login_control.py ADDED
@@ -0,0 +1,164 @@
1
+ """The aidot integration."""
2
+
3
+ from homeassistant.core import HomeAssistant
4
+ import aiohttp
5
+ import logging
6
+ from .login_data import LoginData
7
+
8
+ import base64
9
+ from cryptography.hazmat.backends import default_backend
10
+ from cryptography.hazmat.primitives import serialization
11
+ from cryptography.hazmat.primitives.asymmetric import rsa
12
+ from cryptography.hazmat.primitives.asymmetric import padding
13
+ from cryptography.hazmat.primitives import hashes
14
+
15
+ from .login_const import APP_ID, PUBLIC_KEY_PEM
16
+ _LOGGER = logging.getLogger(__name__)
17
+
18
+ def rsa_password_encrypt(message: str):
19
+ """Get password rsa encrypt."""
20
+ public_key = serialization.load_pem_public_key(
21
+ PUBLIC_KEY_PEM, backend=default_backend()
22
+ )
23
+
24
+ encrypted = public_key.encrypt(
25
+ message.encode("utf-8"),
26
+ padding.PKCS1v15(),
27
+ )
28
+
29
+ encrypted_base64 = base64.b64encode(encrypted).decode("utf-8")
30
+
31
+ return encrypted_base64
32
+
33
+
34
+ class LoginControl:
35
+ _instance = None # singleton
36
+
37
+ def __new__(cls, *args, **kwargs):
38
+ if cls._instance is None:
39
+ cls._instance = super().__new__(cls)
40
+ return cls._instance
41
+
42
+ def __init__(self) -> None:
43
+ self.LoginData = LoginData()
44
+
45
+ def change_country_code(self, selected_contry_obj: str):
46
+ """Do change_country_code."""
47
+ self.LoginData.baseUrl = (
48
+ f"https://prod-{selected_contry_obj['region'].lower()}-api.arnoo.com/v17"
49
+ )
50
+
51
+ async def async_get_products(
52
+ self, hass: HomeAssistant, token: str, product_ids: str
53
+ ):
54
+ """Get device list."""
55
+ url = f"{self.LoginData.baseUrl}/products/{product_ids}"
56
+ headers = {
57
+ "Terminal": "app",
58
+ "Token": token,
59
+ "Appid": APP_ID,
60
+ }
61
+
62
+ session = hass.helpers.aiohttp_client.async_get_clientsession()
63
+
64
+ try:
65
+ async with session.get(url, headers=headers) as response:
66
+ response.raise_for_status()
67
+ response_data = await response.json()
68
+ return response_data
69
+ except aiohttp.ClientError as e:
70
+ _LOGGER.info("async_get_products ClientError {e}")
71
+ return None
72
+
73
+ async def async_get_devices(self, hass: HomeAssistant, token: str, house_id: str):
74
+ """Get device list."""
75
+
76
+ url = f"{self.LoginData.baseUrl}/devices?houseId={house_id}"
77
+ headers = {
78
+ "Terminal": "app",
79
+ "Token": token,
80
+ "Appid": APP_ID,
81
+ }
82
+
83
+ session = hass.helpers.aiohttp_client.async_get_clientsession()
84
+
85
+ try:
86
+ async with session.get(url, headers=headers) as response:
87
+ response.raise_for_status()
88
+ response_data = await response.json()
89
+ return response_data
90
+ except aiohttp.ClientError as e:
91
+ _LOGGER.info("async_get_devices ClientError {e}")
92
+ return None
93
+
94
+ async def async_get_houses(self, hass: HomeAssistant, token: str):
95
+ """Get house list."""
96
+
97
+ url = f"{self.LoginData.baseUrl}/houses"
98
+ headers = {
99
+ "Terminal": "app",
100
+ "Token": token,
101
+ "Appid": APP_ID,
102
+ }
103
+
104
+ session = hass.helpers.aiohttp_client.async_get_clientsession()
105
+
106
+ try:
107
+ async with session.get(url, headers=headers) as response:
108
+ response.raise_for_status()
109
+ response_data = await response.json()
110
+ return response_data
111
+ except aiohttp.ClientError as e:
112
+ _LOGGER.info("async_get_houses ClientError {e}")
113
+ return None
114
+
115
+ async def async_post_login(self, hass: HomeAssistant, username: str, password: str):
116
+ """Login the user input allows us to connect."""
117
+
118
+ url = f"{self.LoginData.baseUrl}/users/loginWithFreeVerification"
119
+ headers = {"Appid": APP_ID, "Terminal": "app"}
120
+ data = {
121
+ "countryKey": "region:UnitedStates",
122
+ "username": username,
123
+ "password": rsa_password_encrypt(password),
124
+ "terminalId": "gvz3gjae10l4zii00t7y0",
125
+ "webVersion": "0.5.0",
126
+ "area": "Asia/Shanghai",
127
+ "UTC": "UTC+8",
128
+ }
129
+ session = hass.helpers.aiohttp_client.async_get_clientsession()
130
+
131
+ try:
132
+ async with session.post(url, headers=headers, json=data) as response:
133
+ response.raise_for_status()
134
+ login_response = await response.json()
135
+ return login_response
136
+ except aiohttp.ClientError as e:
137
+ _LOGGER.info("async_post_login ClientError {e}")
138
+ return None
139
+
140
+ async def async_get_all_login_info(
141
+ self, hass: HomeAssistant, username: str, password: str
142
+ ):
143
+ """Get get all login info."""
144
+ # login in
145
+ login_response = await self.async_post_login(
146
+ hass,
147
+ username,
148
+ password,
149
+ )
150
+ accessToken = login_response["accessToken"]
151
+
152
+ # get houses
153
+ default_house = await self.async_get_houses(hass, accessToken)
154
+
155
+ # get device_list
156
+ device_list = await self.async_get_devices(
157
+ hass, accessToken, default_house["id"]
158
+ )
159
+
160
+ # get product_list
161
+ productIds = ",".join([item["productId"] for item in device_list])
162
+ product_list = await self.async_get_products(hass, accessToken, productIds)
163
+
164
+ return (login_response, default_house, device_list, product_list)
aidot/login_data.py ADDED
@@ -0,0 +1,16 @@
1
+ """The aidot integration."""
2
+
3
+ from .login_const import BASE_URL
4
+
5
+
6
+ class LoginData:
7
+
8
+ _instance = None # singleton
9
+
10
+ def __new__(cls, *args, **kwargs):
11
+ if cls._instance is None:
12
+ cls._instance = super().__new__(cls)
13
+ return cls._instance
14
+
15
+ def __init__(self) -> None:
16
+ self.baseUrl = BASE_URL
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2024 Fabian Affolter <fabian@affolter-engineering.ch>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.1
2
+ Name: python-aidot
3
+ Version: 0.1.0
4
+ Summary: aidot control wifi lights
5
+ Author: aidotdev2024
6
+ Classifier: Programming Language :: Python :: 3.11
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: requests
12
+ Requires-Dist: aiohttp
13
+ Requires-Dist: setuptools
14
+
15
+
16
+ aidot is a Python library to control the aidot Wi-Fi Lights with home assistant.
@@ -0,0 +1,12 @@
1
+ aidot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ aidot/aes_utils.py,sha256=lCOJ0VXx9REn6ot1jCFK8o-oY-fQ0amcDAbOFwqauu0,950
3
+ aidot/discover.py,sha256=lict_jTNiSRg57Lo_6uixqklUQy0H34G5z46Xgf_hoE,2359
4
+ aidot/lan.py,sha256=g2vjwZViS9EeM7AYc3-mN0ZJ8iXx0TDaNm_IKEzkiVo,9527
5
+ aidot/login_const.py,sha256=-bQBeq_cqGM5IT53hSlvr1SJ2WC8G7NHE7EzI63OXrY,436
6
+ aidot/login_control.py,sha256=WkvdTKJjwuyGSSorTwkLD-rRRy5glwADRVcFo2FsuiA,5610
7
+ aidot/login_data.py,sha256=bw417da8bkd71KnAvlPEgmSgwvjiI3b23mqmyOq3Tfc,351
8
+ python_aidot-0.1.0.dist-info/LICENSE,sha256=EZzvMisn9C0Z2kwzKMqGE_xljuPxjA9bwUetwX_1nwM,1141
9
+ python_aidot-0.1.0.dist-info/METADATA,sha256=nh28oHlMcW_ohQU4JbpXs0Jom7ScT-_rTldBEzm4y8U,495
10
+ python_aidot-0.1.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
11
+ python_aidot-0.1.0.dist-info/top_level.txt,sha256=_doNL2OOnXeinm1X72eH2wz26wAkZwbM47KhNB6_QzI,6
12
+ python_aidot-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.43.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ aidot