python-aidot-cameras 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 +31 -0
- aidot/aes_utils.py +46 -0
- aidot/client.py +405 -0
- aidot/const.py +241 -0
- aidot/credentials.py +158 -0
- aidot/device_client.py +12209 -0
- aidot/discover.py +229 -0
- aidot/exceptions.py +58 -0
- aidot/g711.py +54 -0
- aidot/login_const.py +13 -0
- python_aidot_cameras-0.1.0.dist-info/METADATA +61 -0
- python_aidot_cameras-0.1.0.dist-info/RECORD +15 -0
- python_aidot_cameras-0.1.0.dist-info/WHEEL +5 -0
- python_aidot_cameras-0.1.0.dist-info/licenses/LICENSE +21 -0
- python_aidot_cameras-0.1.0.dist-info/top_level.txt +1 -0
aidot/const.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from enum import StrEnum, IntEnum
|
|
2
|
+
|
|
3
|
+
SUPPORTED_COUNTRYS = [
|
|
4
|
+
{"_id": "1-0", "id": "AL", "name": "Albania", "ext": "", "region": "EU"},
|
|
5
|
+
{
|
|
6
|
+
"_id": "1-1",
|
|
7
|
+
"id": "AG",
|
|
8
|
+
"name": "Antigua and Barbuda",
|
|
9
|
+
"ext": "",
|
|
10
|
+
"region": "US",
|
|
11
|
+
},
|
|
12
|
+
{"_id": "1-2", "id": "AR", "name": "Argentina", "ext": "", "region": "US"},
|
|
13
|
+
{"_id": "1-3", "id": "AT", "name": "Austria", "ext": "", "region": "EU"},
|
|
14
|
+
{"_id": "1-4", "id": "AI", "name": "Anguilla", "ext": "", "region": "US"},
|
|
15
|
+
{"_id": "1-5", "id": "AF", "name": "Afghanistan", "ext": "", "region": "JP"},
|
|
16
|
+
{"_id": "1-6", "id": "AM", "name": "Armenia", "ext": "", "region": "JP"},
|
|
17
|
+
{"_id": "1-7", "id": "AZ", "name": "Azerbaijan", "ext": "", "region": "JP"},
|
|
18
|
+
{"_id": "1-8", "id": "AU", "name": "Australia", "ext": "", "region": "JP"},
|
|
19
|
+
{"_id": "2-0", "id": "BO", "name": "Bolivia", "ext": "", "region": "US"},
|
|
20
|
+
{"_id": "2-1", "id": "BR", "name": "Brazil", "ext": "", "region": "US"},
|
|
21
|
+
{"_id": "2-2", "id": "BG", "name": "Bulgaria", "ext": "", "region": "EU"},
|
|
22
|
+
{"_id": "2-3", "id": "BS", "name": "Bahamas", "ext": "", "region": "US"},
|
|
23
|
+
{"_id": "2-4", "id": "BE", "name": "Belgium", "ext": "", "region": "EU"},
|
|
24
|
+
{"_id": "2-5", "id": "BZ", "name": "Belize", "ext": "", "region": "US"},
|
|
25
|
+
{"_id": "2-6", "id": "BB", "name": "Barbados", "ext": "", "region": "US"},
|
|
26
|
+
{"_id": "2-7", "id": "BM", "name": "Bermuda", "ext": "", "region": "US"},
|
|
27
|
+
{"_id": "2-8", "id": "BN", "name": "Brunei", "ext": "", "region": "JP"},
|
|
28
|
+
{"_id": "2-9", "id": "BH", "name": "Bahrain", "ext": "", "region": "JP"},
|
|
29
|
+
{"_id": "2-10", "id": "BD", "name": "Bangladesh", "ext": "", "region": "JP"},
|
|
30
|
+
{"_id": "2-11", "id": "BT", "name": "Bhutan", "ext": "", "region": "JP"},
|
|
31
|
+
{"_id": "3-0", "id": "CA", "name": "Canada", "ext": "", "region": "US"},
|
|
32
|
+
{"_id": "3-1", "id": "CL", "name": "Chile", "ext": "", "region": "US"},
|
|
33
|
+
{"_id": "3-2", "id": "CO", "name": "Colombia", "ext": "", "region": "US"},
|
|
34
|
+
{"_id": "3-3", "id": "CR", "name": "Costa Rica", "ext": "", "region": "US"},
|
|
35
|
+
{"_id": "3-4", "id": "CU", "name": "Cuba", "ext": "", "region": "US"},
|
|
36
|
+
{"_id": "3-5", "id": "CZ", "name": "Czech Republic", "ext": "", "region": "EU"},
|
|
37
|
+
{"_id": "3-6", "id": "HR", "name": "Croatia", "ext": "", "region": "EU"},
|
|
38
|
+
{"_id": "3-7", "id": "KY", "name": "Cayman Islands", "ext": "", "region": "US"},
|
|
39
|
+
{"_id": "3-8", "id": "KH", "name": "Cambodia", "ext": "", "region": "JP"},
|
|
40
|
+
{"_id": "3-9", "id": "CY", "name": "Cyprus", "ext": "", "region": "JP"},
|
|
41
|
+
{"_id": "4-0", "id": "DK", "name": "Denmark", "ext": "", "region": "EU"},
|
|
42
|
+
{"_id": "4-1", "id": "DO", "name": "Dominican Republic", "ext": "", "region": "US"},
|
|
43
|
+
{"_id": "5-0", "id": "EC", "name": "Ecuador", "ext": "", "region": "US"},
|
|
44
|
+
{"_id": "5-1", "id": "SV", "name": "El Salvador", "ext": "", "region": "US"},
|
|
45
|
+
{"_id": "5-2", "id": "EE", "name": "Estonia", "ext": "", "region": "EU"},
|
|
46
|
+
{"_id": "6-0", "id": "FI", "name": "Finland", "ext": "", "region": "EU"},
|
|
47
|
+
{"_id": "6-1", "id": "FR", "name": "France", "ext": "", "region": "EU"},
|
|
48
|
+
{"_id": "6-2", "id": "GF", "name": "French Guiana", "ext": "", "region": "US"},
|
|
49
|
+
{"_id": "7-0", "id": "GR", "name": "Greece", "ext": "", "region": "EU"},
|
|
50
|
+
{"_id": "7-1", "id": "GT", "name": "Guatemala", "ext": "", "region": "US"},
|
|
51
|
+
{"_id": "7-2", "id": "GY", "name": "Guyana", "ext": "", "region": "US"},
|
|
52
|
+
{"_id": "7-3", "id": "DE", "name": "Germany", "ext": "", "region": "EU"},
|
|
53
|
+
{"_id": "7-4", "id": "GD", "name": "Grenada", "ext": "", "region": "US"},
|
|
54
|
+
{"_id": "7-5", "id": "GE", "name": "Georgia", "ext": "", "region": "JP"},
|
|
55
|
+
{"_id": "8-0", "id": "HT", "name": "Haiti", "ext": "", "region": "US"},
|
|
56
|
+
{"_id": "8-1", "id": "HN", "name": "Honduras", "ext": "", "region": "US"},
|
|
57
|
+
{"_id": "8-2", "id": "HU", "name": "Hungary", "ext": "", "region": "EU"},
|
|
58
|
+
{"_id": "9-0", "id": "IS", "name": "Iceland", "ext": "", "region": "EU"},
|
|
59
|
+
{"_id": "9-1", "id": "IE", "name": "Ireland", "ext": "", "region": "EU"},
|
|
60
|
+
{"_id": "9-2", "id": "IT", "name": "Italy", "ext": "", "region": "EU"},
|
|
61
|
+
{"_id": "9-3", "id": "IN", "name": "India", "ext": "", "region": "JP"},
|
|
62
|
+
{"_id": "9-4", "id": "ID", "name": "Indonesia", "ext": "", "region": "JP"},
|
|
63
|
+
{"_id": "9-5", "id": "IR", "name": "Iran", "ext": "", "region": "JP"},
|
|
64
|
+
{"_id": "9-6", "id": "IQ", "name": "Iraq", "ext": "", "region": "JP"},
|
|
65
|
+
{"_id": "9-7", "id": "IL", "name": "Israel", "ext": "", "region": "EU"},
|
|
66
|
+
{"_id": "10-0", "id": "JM", "name": "Jamaica", "ext": "", "region": "US"},
|
|
67
|
+
{"_id": "10-1", "id": "JP", "name": "Japan", "ext": "", "region": "JP"},
|
|
68
|
+
{"_id": "10-2", "id": "JO", "name": "Jordan", "ext": "", "region": "JP"},
|
|
69
|
+
{"_id": "11-0", "id": "KZ", "name": "Kazakhstan", "ext": "", "region": "JP"},
|
|
70
|
+
{"_id": "11-1", "id": "KR", "name": "Korea", "ext": "", "region": "JP"},
|
|
71
|
+
{"_id": "11-2", "id": "KW", "name": "Kuwait", "ext": "", "region": "JP"},
|
|
72
|
+
{"_id": "11-3", "id": "KG", "name": "Kyrgyzstan", "ext": "", "region": "JP"},
|
|
73
|
+
{"_id": "12-0", "id": "LV", "name": "Latvia", "ext": "", "region": "EU"},
|
|
74
|
+
{"_id": "12-1", "id": "LT", "name": "Lithuania", "ext": "", "region": "EU"},
|
|
75
|
+
{"_id": "12-2", "id": "LU", "name": "Luxembourg", "ext": "", "region": "EU"},
|
|
76
|
+
{"_id": "12-3", "id": "LI", "name": "Liechtenstein", "ext": "", "region": "EU"},
|
|
77
|
+
{"_id": "12-4", "id": "LA", "name": "Laos", "ext": "", "region": "JP"},
|
|
78
|
+
{"_id": "12-5", "id": "LB", "name": "Lebanon", "ext": "", "region": "JP"},
|
|
79
|
+
{"_id": "13-0", "id": "MT", "name": "Malta", "ext": "", "region": "EU"},
|
|
80
|
+
{"_id": "13-1", "id": "MX", "name": "Mexico", "ext": "", "region": "US"},
|
|
81
|
+
{"_id": "13-2", "id": "MD", "name": "Moldova", "ext": "", "region": "EU"},
|
|
82
|
+
{"_id": "13-3", "id": "MC", "name": "Monaco", "ext": "", "region": "EU"},
|
|
83
|
+
{"_id": "13-4", "id": "MS", "name": "Montserrat", "ext": "", "region": "US"},
|
|
84
|
+
{"_id": "13-5", "id": "ME", "name": "Montenegro", "ext": "", "region": "EU"},
|
|
85
|
+
{"_id": "13-6", "id": "MY", "name": "Malaysia", "ext": "", "region": "JP"},
|
|
86
|
+
{"_id": "13-7", "id": "MV", "name": "Maldives", "ext": "", "region": "JP"},
|
|
87
|
+
{"_id": "13-8", "id": "MN", "name": "Mongolia", "ext": "", "region": "JP"},
|
|
88
|
+
{"_id": "13-9", "id": "MM", "name": "Myanmar", "ext": "", "region": "JP"},
|
|
89
|
+
{"_id": "14-0", "id": "NL", "name": "Netherlands", "ext": "", "region": "EU"},
|
|
90
|
+
{"_id": "14-1", "id": "NI", "name": "Nicaragua", "ext": "", "region": "US"},
|
|
91
|
+
{"_id": "14-2", "id": "NO", "name": "Norway", "ext": "", "region": "EU"},
|
|
92
|
+
{"_id": "14-3", "id": "MK", "name": "North Macedonia", "ext": "", "region": "EU"},
|
|
93
|
+
{"_id": "14-4", "id": "NZ", "name": "New Zealand", "ext": "", "region": "JP"},
|
|
94
|
+
{"_id": "14-5", "id": "NP", "name": "Nepal", "ext": "", "region": "JP"},
|
|
95
|
+
{"_id": "14-6", "id": "KP", "name": "North Korea", "ext": "", "region": "JP"},
|
|
96
|
+
{"_id": "15-0", "id": "OM", "name": "Oman", "ext": "", "region": "JP"},
|
|
97
|
+
{"_id": "16-0", "id": "PA", "name": "Panama", "ext": "", "region": "US"},
|
|
98
|
+
{"_id": "16-1", "id": "PY", "name": "Paraguay", "ext": "", "region": "US"},
|
|
99
|
+
{"_id": "16-2", "id": "PE", "name": "Peru", "ext": "", "region": "US"},
|
|
100
|
+
{"_id": "16-3", "id": "PT", "name": "Portugal", "ext": "", "region": "EU"},
|
|
101
|
+
{"_id": "16-4", "id": "PK", "name": "Pakistan", "ext": "", "region": "JP"},
|
|
102
|
+
{"_id": "16-5", "id": "PS", "name": "Palestine", "ext": "", "region": "JP"},
|
|
103
|
+
{"_id": "16-6", "id": "PH", "name": "Philippines", "ext": "", "region": "JP"},
|
|
104
|
+
{"_id": "17-0", "id": "QA", "name": "Qatar", "ext": "", "region": "JP"},
|
|
105
|
+
{"_id": "18-0", "id": "RO", "name": "Romania", "ext": "", "region": "EU"},
|
|
106
|
+
{"_id": "18-1", "id": "RU", "name": "Russia", "ext": "", "region": "EU"},
|
|
107
|
+
{"_id": "19-0", "id": "SM", "name": "San Marino", "ext": "", "region": "EU"},
|
|
108
|
+
{"_id": "19-1", "id": "SI", "name": "Slovenia", "ext": "", "region": "EU"},
|
|
109
|
+
{"_id": "19-2", "id": "ES", "name": "Spain", "ext": "", "region": "EU"},
|
|
110
|
+
{"_id": "19-3", "id": "LC", "name": "St.Lucia", "ext": "", "region": "US"},
|
|
111
|
+
{"_id": "19-4", "id": "SR", "name": "Suriname", "ext": "", "region": "US"},
|
|
112
|
+
{"_id": "19-5", "id": "SE", "name": "Sweden", "ext": "", "region": "EU"},
|
|
113
|
+
{"_id": "19-6", "id": "SK", "name": "Slovakia", "ext": "", "region": "EU"},
|
|
114
|
+
{"_id": "19-7", "id": "RS", "name": "Serbia", "ext": "", "region": "EU"},
|
|
115
|
+
{
|
|
116
|
+
"_id": "19-8",
|
|
117
|
+
"id": "KN",
|
|
118
|
+
"name": "St.Kitts and Nevis",
|
|
119
|
+
"ext": "",
|
|
120
|
+
"region": "US",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"_id": "19-9",
|
|
124
|
+
"id": "VC",
|
|
125
|
+
"name": "St.Vincent and the Grenadines",
|
|
126
|
+
"ext": "",
|
|
127
|
+
"region": "US",
|
|
128
|
+
},
|
|
129
|
+
{"_id": "19-10", "id": "SA", "name": "Saudi Arabia", "ext": "", "region": "JP"},
|
|
130
|
+
{"_id": "19-11", "id": "SG", "name": "Singapore", "ext": "", "region": "JP"},
|
|
131
|
+
{"_id": "19-12", "id": "LK", "name": "Sri Lanka", "ext": "", "region": "JP"},
|
|
132
|
+
{"_id": "19-13", "id": "SY", "name": "Syria", "ext": "", "region": "JP"},
|
|
133
|
+
{"_id": "19-14", "id": "CH", "name": "Switzerland", "ext": "", "region": "EU"},
|
|
134
|
+
{
|
|
135
|
+
"_id": "20-0",
|
|
136
|
+
"id": "TT",
|
|
137
|
+
"name": "Trinidad and Tobago",
|
|
138
|
+
"ext": "",
|
|
139
|
+
"region": "US",
|
|
140
|
+
},
|
|
141
|
+
{"_id": "20-1", "id": "TC", "name": "Turks & Caicos", "ext": "", "region": "US"},
|
|
142
|
+
{"_id": "20-2", "id": "TJ", "name": "Tajikistan", "ext": "", "region": "JP"},
|
|
143
|
+
{"_id": "20-3", "id": "TH", "name": "Thailand", "ext": "", "region": "JP"},
|
|
144
|
+
{"_id": "20-4", "id": "TG", "name": "Togo", "ext": "", "region": "JP"},
|
|
145
|
+
{"_id": "20-5", "id": "TR", "name": "Turkey", "ext": "", "region": "EU"},
|
|
146
|
+
{"_id": "20-6", "id": "TM", "name": "Turkmenistan", "ext": "", "region": "JP"},
|
|
147
|
+
{"_id": "21-0", "id": "UA", "name": "Ukraine", "ext": "", "region": "EU"},
|
|
148
|
+
{"_id": "21-1", "id": "GB", "name": "United Kingdom", "ext": "", "region": "EU"},
|
|
149
|
+
{"_id": "21-2", "id": "US", "name": "United States", "ext": "", "region": "US"},
|
|
150
|
+
{"_id": "21-3", "id": "UY", "name": "Uruguay", "ext": "", "region": "US"},
|
|
151
|
+
{
|
|
152
|
+
"_id": "21-4",
|
|
153
|
+
"id": "AE",
|
|
154
|
+
"name": "United Arab Emirates",
|
|
155
|
+
"ext": "",
|
|
156
|
+
"region": "JP",
|
|
157
|
+
},
|
|
158
|
+
{"_id": "21-5", "id": "UZ", "name": "Uzbekistan", "ext": "", "region": "JP"},
|
|
159
|
+
{"_id": "22-0", "id": "VE", "name": "Venezuela", "ext": "", "region": "US"},
|
|
160
|
+
{"_id": "22-1", "id": "VG", "name": "Virgin Islands", "ext": "", "region": "US"},
|
|
161
|
+
{"_id": "22-2", "id": "VN", "name": "Vietnam", "ext": "", "region": "JP"},
|
|
162
|
+
{"_id": "23-0", "id": "YE", "name": "Yemen", "ext": "", "region": "JP"},
|
|
163
|
+
]
|
|
164
|
+
SUPPORTED_COUNTRY_CODES = [item["id"] for item in SUPPORTED_COUNTRYS]
|
|
165
|
+
SUPPORTED_COUNTRY_NAMES = [item["name"] for item in SUPPORTED_COUNTRYS]
|
|
166
|
+
DEFAULT_COUNTRY_NAME = "United States"
|
|
167
|
+
DEFAULT_COUNTRY_CODE = "US"
|
|
168
|
+
CONF_APP_ID = "Appid"
|
|
169
|
+
CONF_TERMINAL = "Terminal"
|
|
170
|
+
CONF_LOGIN_RESPONSE = "login_response"
|
|
171
|
+
CONF_LOGIN_INFO = "login_info"
|
|
172
|
+
CONF_SELECTED_HOUSE = "selected_house"
|
|
173
|
+
CONF_DEVICE_LIST = "device_list"
|
|
174
|
+
CONF_PRODUCT_LIST = "product_list"
|
|
175
|
+
CONF_ACCESS_TOKEN = "accessToken"
|
|
176
|
+
CONF_REFRESH_TOKEN = "refreshToken"
|
|
177
|
+
CONF_TOKEN = "Token"
|
|
178
|
+
CONF_USERNAME = "username"
|
|
179
|
+
CONF_PASSWORD = "password"
|
|
180
|
+
CONF_REGION = "region"
|
|
181
|
+
CONF_COUNTRY = "country"
|
|
182
|
+
CONF_ID = "id"
|
|
183
|
+
CONF_NAME = "name"
|
|
184
|
+
CONF_PRODUCT_ID = "productId"
|
|
185
|
+
CONF_PRODUCT = "product"
|
|
186
|
+
CONF_IS_DEFAULT = "isDefault"
|
|
187
|
+
CONF_TYPE = "type"
|
|
188
|
+
CONF_MODEL_ID = "modelId"
|
|
189
|
+
CONF_MAC = "mac"
|
|
190
|
+
CONF_AES_KEY = "aesKey"
|
|
191
|
+
CONF_HARDWARE_VERSION = "hardwareVersion"
|
|
192
|
+
CONF_SERVICE_MODULES = "serviceModules"
|
|
193
|
+
CONF_IDENTITY = "identity"
|
|
194
|
+
CONF_PROPERTIES = "properties"
|
|
195
|
+
CONF_MINVALUE = "minValue"
|
|
196
|
+
CONF_MAXVALUE = "maxValue"
|
|
197
|
+
CONF_IPADDRESS = "ipAddress"
|
|
198
|
+
CONF_CODE = "code"
|
|
199
|
+
CONF_PAYLOAD = "payload"
|
|
200
|
+
CONF_ASCNUMBER = "ascNumber"
|
|
201
|
+
CONF_ATTR = "attr"
|
|
202
|
+
CONF_ON_OFF = "OnOff"
|
|
203
|
+
CONF_DIMMING = "Dimming"
|
|
204
|
+
CONF_RGBW = "RGBW"
|
|
205
|
+
CONF_CCT = "CCT"
|
|
206
|
+
CONF_ACK = "ack"
|
|
207
|
+
CONF_IS_OWNER = "isOwner"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class Identity(StrEnum):
|
|
211
|
+
"""Available entity identity."""
|
|
212
|
+
|
|
213
|
+
RGBW = "control.light.rgbw"
|
|
214
|
+
CCT = "control.light.cct"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class Attribute(StrEnum):
|
|
218
|
+
"""Available entity attributes."""
|
|
219
|
+
|
|
220
|
+
RGBW = "rgbw"
|
|
221
|
+
CCT = "cct"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class ServerErrorCode(IntEnum):
|
|
225
|
+
TOKEN_EXPIRED = 21026
|
|
226
|
+
LOGIN_INVALID = 21025
|
|
227
|
+
USER_PWD_INCORRECT = 560080
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# ── Cloud API credentials ──────────────────────────────────────────────────── #
|
|
231
|
+
|
|
232
|
+
APP_ID = "1383974540041977857"
|
|
233
|
+
BASE_URL = "https://prod-us-api.arnoo.com/v17"
|
|
234
|
+
|
|
235
|
+
PUBLIC_KEY_PEM = b"""-----BEGIN PUBLIC KEY-----
|
|
236
|
+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtQAnPCi8ksPnS1Du6z96PsKfN
|
|
237
|
+
p2Gp/f/bHwlrAdplbX3p7/TnGpnbJGkLq8uRxf6cw+vOthTsZjkPCF7CatRvRnTj
|
|
238
|
+
c9fcy7yE0oXa5TloYyXD6GkxgftBbN/movkJJGQCc7gFavuYoAdTRBOyQoXBtm0m
|
|
239
|
+
kXMSjXOldI/290b9BQIDAQAB
|
|
240
|
+
-----END PUBLIC KEY-----
|
|
241
|
+
"""
|
aidot/credentials.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Credential storage for AiDot CLI tools.
|
|
2
|
+
|
|
3
|
+
Priority order:
|
|
4
|
+
1. Environment variables: AIDOT_USERNAME, AIDOT_PASSWORD, AIDOT_COUNTRY
|
|
5
|
+
Works in Docker containers, Home Assistant addons, CI systems.
|
|
6
|
+
2. Fernet-encrypted file pair at ~/.config/aidot/:
|
|
7
|
+
credentials.enc - ciphertext (0600)
|
|
8
|
+
.key - random symmetric key (0600)
|
|
9
|
+
Cross-platform, no OS-specific APIs required.
|
|
10
|
+
3. Plain JSON file at ~/.config/aidot/credentials.json (0600)
|
|
11
|
+
Legacy fallback; auto-migrated to encrypted on first load.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import stat
|
|
17
|
+
|
|
18
|
+
_CONFIG_DIR = os.path.expanduser("~/.config/aidot")
|
|
19
|
+
_DEFAULT_CREDS_FILE = os.path.join(_CONFIG_DIR, "credentials.json")
|
|
20
|
+
_ENC_FILE = os.path.join(_CONFIG_DIR, "credentials.enc")
|
|
21
|
+
_KEY_FILE = os.path.join(_CONFIG_DIR, ".key")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── Public API ───────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
def load_credentials(creds_path: str | None = None) -> dict:
|
|
27
|
+
"""Return {"username": ..., "password": ..., "country": ...}.
|
|
28
|
+
|
|
29
|
+
Checks environment variables first, then encrypted file, then plain JSON.
|
|
30
|
+
Raises FileNotFoundError / ValueError if nothing is found.
|
|
31
|
+
"""
|
|
32
|
+
# Priority 1: environment variables
|
|
33
|
+
env_user = os.environ.get("AIDOT_USERNAME")
|
|
34
|
+
env_pass = os.environ.get("AIDOT_PASSWORD")
|
|
35
|
+
if env_user and env_pass:
|
|
36
|
+
return {
|
|
37
|
+
"username": env_user,
|
|
38
|
+
"password": env_pass,
|
|
39
|
+
"country": os.environ.get("AIDOT_COUNTRY", "US"),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Priority 2: Fernet-encrypted file
|
|
43
|
+
enc_path = (creds_path + ".enc") if creds_path else _ENC_FILE
|
|
44
|
+
key_path = (creds_path + ".key") if creds_path else _KEY_FILE
|
|
45
|
+
if os.path.exists(enc_path) and os.path.exists(key_path):
|
|
46
|
+
try:
|
|
47
|
+
return _load_encrypted(enc_path, key_path)
|
|
48
|
+
except Exception as exc:
|
|
49
|
+
raise ValueError(f"Failed to decrypt {enc_path}: {exc}") from exc
|
|
50
|
+
|
|
51
|
+
# Priority 3: plain JSON (legacy)
|
|
52
|
+
plain = creds_path or _DEFAULT_CREDS_FILE
|
|
53
|
+
if os.path.exists(plain):
|
|
54
|
+
data = _load_plain(plain)
|
|
55
|
+
# Auto-migrate to encrypted storage
|
|
56
|
+
try:
|
|
57
|
+
_save_encrypted(data["username"], data["password"],
|
|
58
|
+
data.get("country", "US"), enc_path, key_path)
|
|
59
|
+
os.unlink(plain)
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
raise FileNotFoundError(
|
|
65
|
+
"No credentials found. Options:\n"
|
|
66
|
+
" • Set AIDOT_USERNAME / AIDOT_PASSWORD / AIDOT_COUNTRY env vars\n"
|
|
67
|
+
" • Run with --save-credentials to store them encrypted\n"
|
|
68
|
+
f" • Place credentials.json in {_CONFIG_DIR}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def save_credentials(
|
|
73
|
+
username: str,
|
|
74
|
+
password: str,
|
|
75
|
+
country: str = "US",
|
|
76
|
+
creds_path: str | None = None,
|
|
77
|
+
) -> str:
|
|
78
|
+
"""Encrypt and store credentials. Returns the path where they were saved."""
|
|
79
|
+
enc_path = (creds_path + ".enc") if creds_path else _ENC_FILE
|
|
80
|
+
key_path = (creds_path + ".key") if creds_path else _KEY_FILE
|
|
81
|
+
_save_encrypted(username, password, country, enc_path, key_path)
|
|
82
|
+
return enc_path
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def delete_credentials(creds_path: str | None = None) -> None:
|
|
86
|
+
"""Remove stored credentials (encrypted and/or plain JSON)."""
|
|
87
|
+
enc_path = (creds_path + ".enc") if creds_path else _ENC_FILE
|
|
88
|
+
key_path = (creds_path + ".key") if creds_path else _KEY_FILE
|
|
89
|
+
plain = creds_path or _DEFAULT_CREDS_FILE
|
|
90
|
+
for path in (enc_path, key_path, plain):
|
|
91
|
+
if os.path.exists(path):
|
|
92
|
+
try:
|
|
93
|
+
os.unlink(path)
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ── Internal helpers ─────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
def _fernet():
|
|
101
|
+
try:
|
|
102
|
+
from cryptography.fernet import Fernet
|
|
103
|
+
return Fernet
|
|
104
|
+
except ImportError as exc:
|
|
105
|
+
raise ImportError(
|
|
106
|
+
"The 'cryptography' package is required for encrypted credentials. "
|
|
107
|
+
"Install it with: pip install cryptography"
|
|
108
|
+
) from exc
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _load_encrypted(enc_path: str, key_path: str) -> dict:
|
|
112
|
+
Fernet = _fernet()
|
|
113
|
+
with open(key_path, "rb") as f:
|
|
114
|
+
key = f.read().strip()
|
|
115
|
+
with open(enc_path, "rb") as f:
|
|
116
|
+
token = f.read().strip()
|
|
117
|
+
plaintext = Fernet(key).decrypt(token)
|
|
118
|
+
data = json.loads(plaintext)
|
|
119
|
+
for field in ("username", "password"):
|
|
120
|
+
if field not in data:
|
|
121
|
+
raise ValueError(f"Encrypted credentials missing field {field!r}")
|
|
122
|
+
return data
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _save_encrypted(
|
|
126
|
+
username: str, password: str, country: str,
|
|
127
|
+
enc_path: str, key_path: str,
|
|
128
|
+
) -> None:
|
|
129
|
+
Fernet = _fernet()
|
|
130
|
+
# Generate or reuse key - _write_secret handles makedirs
|
|
131
|
+
if os.path.exists(key_path):
|
|
132
|
+
with open(key_path, "rb") as f:
|
|
133
|
+
key = f.read().strip()
|
|
134
|
+
else:
|
|
135
|
+
key = Fernet.generate_key()
|
|
136
|
+
_write_secret(key_path, key)
|
|
137
|
+
|
|
138
|
+
payload = json.dumps({"username": username, "password": password,
|
|
139
|
+
"country": country}).encode()
|
|
140
|
+
_write_secret(enc_path, Fernet(key).encrypt(payload))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _load_plain(path: str) -> dict:
|
|
144
|
+
with open(path) as f:
|
|
145
|
+
data = json.load(f)
|
|
146
|
+
for field in ("username", "password"):
|
|
147
|
+
if field not in data:
|
|
148
|
+
raise ValueError(f"Credentials file {path!r} missing {field!r}")
|
|
149
|
+
return data
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _write_secret(path: str, data: bytes) -> None:
|
|
153
|
+
"""Write binary data to path with 0600 permissions."""
|
|
154
|
+
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
|
155
|
+
with open(path, "wb") as f:
|
|
156
|
+
f.write(data)
|
|
157
|
+
f.write(b"\n")
|
|
158
|
+
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
|