homebridge-tsvesync 1.0.95 → 1.0.97
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.
- package/CHANGELOG.md +38 -0
- package/README.md +48 -4
- package/dist/accessories/air-purifier.accessory.js +18 -18
- package/dist/accessories/air-purifier.accessory.js.map +1 -1
- package/package.json +2 -2
- package/scripts/README.md +65 -0
- package/scripts/vesync-auth-test.sh +770 -0
- package/src/accessories/air-purifier.accessory.ts +18 -18
- package/scripts/vesync-debug.sh +0 -423
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# VeSync Authentication Type Testing Script
|
|
4
|
+
# Consolidated version - tests both NEW and LEGACY authentication flows
|
|
5
|
+
# Supports regional endpoints, PII redaction, and comprehensive error reporting
|
|
6
|
+
#
|
|
7
|
+
# Usage: ./vesync-auth-test.sh [-v] [-d] [email] [password]
|
|
8
|
+
# ./vesync-auth-test.sh --help
|
|
9
|
+
#
|
|
10
|
+
# Environment variables: VESYNC_USERNAME, VESYNC_PASSWORD
|
|
11
|
+
|
|
12
|
+
set -eo pipefail
|
|
13
|
+
|
|
14
|
+
# Script constants
|
|
15
|
+
SCRIPT_VERSION="2.1.0"
|
|
16
|
+
SCRIPT_NAME="VeSync Authentication Type Detector"
|
|
17
|
+
DEFAULT_TIMEOUT=15
|
|
18
|
+
|
|
19
|
+
# Colors and icons
|
|
20
|
+
RED='\033[0;31m'
|
|
21
|
+
GREEN='\033[0;32m'
|
|
22
|
+
YELLOW='\033[1;33m'
|
|
23
|
+
BLUE='\033[0;34m'
|
|
24
|
+
CYAN='\033[0;36m'
|
|
25
|
+
BOLD='\033[1m'
|
|
26
|
+
NC='\033[0m'
|
|
27
|
+
|
|
28
|
+
SUCCESS="✓"
|
|
29
|
+
FAILURE="✗"
|
|
30
|
+
WARNING="⚠"
|
|
31
|
+
INFO="ℹ"
|
|
32
|
+
ARROW="→"
|
|
33
|
+
|
|
34
|
+
# Global variables
|
|
35
|
+
VERBOSE=false
|
|
36
|
+
DEBUG=false
|
|
37
|
+
EMAIL=""
|
|
38
|
+
PASSWORD=""
|
|
39
|
+
APP_ID=""
|
|
40
|
+
TERMINAL_ID=""
|
|
41
|
+
DEBUG_DIR=""
|
|
42
|
+
|
|
43
|
+
# Function to show usage
|
|
44
|
+
show_usage() {
|
|
45
|
+
printf '%s\n' "${BOLD}${SCRIPT_NAME} v${SCRIPT_VERSION}${NC}"
|
|
46
|
+
printf '\n'
|
|
47
|
+
printf '%s\n' "${BOLD}USAGE:${NC}"
|
|
48
|
+
printf ' %s [OPTIONS] [EMAIL] [PASSWORD]\n' "$0"
|
|
49
|
+
printf '\n'
|
|
50
|
+
printf '%s\n' "${BOLD}OPTIONS:${NC}"
|
|
51
|
+
printf ' -v, --verbose Enable verbose output (show detailed API responses)\n'
|
|
52
|
+
printf ' -d, --debug Enable debug mode (save API responses to files)\n'
|
|
53
|
+
printf ' -h, --help Show this help message\n'
|
|
54
|
+
printf '\n'
|
|
55
|
+
printf '%s\n' "${BOLD}ARGUMENTS:${NC}"
|
|
56
|
+
printf ' EMAIL VeSync account email address\n'
|
|
57
|
+
printf ' PASSWORD VeSync account password\n'
|
|
58
|
+
printf '\n'
|
|
59
|
+
printf '%s\n' "${BOLD}ENVIRONMENT VARIABLES:${NC}"
|
|
60
|
+
printf ' VESYNC_USERNAME VeSync account email (alternative to EMAIL argument)\n'
|
|
61
|
+
printf ' VESYNC_PASSWORD VeSync account password (alternative to PASSWORD argument)\n'
|
|
62
|
+
printf '\n'
|
|
63
|
+
printf '%s\n' "${BOLD}EXAMPLES:${NC}"
|
|
64
|
+
printf ' %s user@example.com mypassword\n' "$0"
|
|
65
|
+
printf ' %s -v user@example.com mypassword\n' "$0"
|
|
66
|
+
printf ' VESYNC_USERNAME=user@example.com VESYNC_PASSWORD=mypassword %s\n' "$0"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Function to log messages
|
|
70
|
+
log() {
|
|
71
|
+
local level=$1
|
|
72
|
+
local message=$2
|
|
73
|
+
local timestamp=$(date '+%H:%M:%S')
|
|
74
|
+
|
|
75
|
+
case $level in
|
|
76
|
+
"ERROR") echo -e "${RED}[${timestamp}] ERROR: ${message}${NC}" >&2 ;;
|
|
77
|
+
"WARN") echo -e "${YELLOW}[${timestamp}] WARN: ${message}${NC}" ;;
|
|
78
|
+
"INFO") echo -e "${BLUE}[${timestamp}] INFO: ${message}${NC}" ;;
|
|
79
|
+
"SUCCESS") echo -e "${GREEN}[${timestamp}] SUCCESS: ${message}${NC}" ;;
|
|
80
|
+
"DEBUG") [[ "$VERBOSE" == "true" ]] && echo -e "${CYAN}[${timestamp}] DEBUG: ${message}${NC}" ;;
|
|
81
|
+
esac
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Function to redact PII
|
|
85
|
+
redact_pii() {
|
|
86
|
+
local input="$1"
|
|
87
|
+
local type="${2:-email}"
|
|
88
|
+
|
|
89
|
+
case $type in
|
|
90
|
+
"email")
|
|
91
|
+
if [[ "$input" =~ ^[^@]+@(.+)$ ]]; then
|
|
92
|
+
echo "***@${BASH_REMATCH[1]}"
|
|
93
|
+
else
|
|
94
|
+
echo "***"
|
|
95
|
+
fi
|
|
96
|
+
;;
|
|
97
|
+
"token"|"password")
|
|
98
|
+
local len=${#input}
|
|
99
|
+
if [[ $len -gt 8 ]]; then
|
|
100
|
+
echo "${input:0:4}...${input: -4}"
|
|
101
|
+
elif [[ $len -gt 0 ]]; then
|
|
102
|
+
echo "***"
|
|
103
|
+
else
|
|
104
|
+
echo ""
|
|
105
|
+
fi
|
|
106
|
+
;;
|
|
107
|
+
"account_id")
|
|
108
|
+
local len=${#input}
|
|
109
|
+
if [[ $len -gt 8 ]]; then
|
|
110
|
+
echo "${input:0:4}...${input: -4}"
|
|
111
|
+
else
|
|
112
|
+
echo "***"
|
|
113
|
+
fi
|
|
114
|
+
;;
|
|
115
|
+
*)
|
|
116
|
+
echo "***"
|
|
117
|
+
;;
|
|
118
|
+
esac
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Function to generate unique identifiers
|
|
122
|
+
generate_app_id() {
|
|
123
|
+
if command -v openssl >/dev/null 2>&1; then
|
|
124
|
+
openssl rand -hex 4
|
|
125
|
+
elif [[ -r /dev/urandom ]]; then
|
|
126
|
+
cat /dev/urandom | LC_ALL=C tr -dc 'a-z0-9' | fold -w 8 | head -n 1
|
|
127
|
+
else
|
|
128
|
+
echo "app$(date +%s | tail -c 5)"
|
|
129
|
+
fi
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
generate_terminal_id() {
|
|
133
|
+
if command -v openssl >/dev/null 2>&1; then
|
|
134
|
+
openssl rand -hex 8
|
|
135
|
+
elif [[ -r /dev/urandom ]]; then
|
|
136
|
+
cat /dev/urandom | LC_ALL=C tr -dc 'a-z0-9' | fold -w 16 | head -n 1
|
|
137
|
+
else
|
|
138
|
+
echo "terminal$(date +%s)"
|
|
139
|
+
fi
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Function to setup debug directory
|
|
143
|
+
setup_debug_dir() {
|
|
144
|
+
if [[ "$DEBUG" == "true" ]]; then
|
|
145
|
+
DEBUG_DIR="/tmp/vesync-auth-debug-$(date +%s)"
|
|
146
|
+
mkdir -p "$DEBUG_DIR"
|
|
147
|
+
log "DEBUG" "Debug directory created: $DEBUG_DIR"
|
|
148
|
+
fi
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# Function to save response to debug file
|
|
152
|
+
save_debug_response() {
|
|
153
|
+
local response="$1"
|
|
154
|
+
local filename="$2"
|
|
155
|
+
|
|
156
|
+
if [[ "$DEBUG" == "true" && -n "$DEBUG_DIR" ]]; then
|
|
157
|
+
if command -v jq >/dev/null 2>&1; then
|
|
158
|
+
echo "$response" | jq '.' > "$DEBUG_DIR/$filename.json" 2>/dev/null || echo "$response" > "$DEBUG_DIR/$filename.txt"
|
|
159
|
+
else
|
|
160
|
+
echo "$response" > "$DEBUG_DIR/$filename.txt"
|
|
161
|
+
fi
|
|
162
|
+
log "DEBUG" "Response saved to: $DEBUG_DIR/$filename"
|
|
163
|
+
fi
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# Function to hash password
|
|
167
|
+
hash_password() {
|
|
168
|
+
local password="$1"
|
|
169
|
+
if command -v md5 >/dev/null 2>&1; then
|
|
170
|
+
echo -n "$password" | md5 -q
|
|
171
|
+
elif command -v md5sum >/dev/null 2>&1; then
|
|
172
|
+
echo -n "$password" | md5sum | cut -d' ' -f1
|
|
173
|
+
else
|
|
174
|
+
log "ERROR" "No MD5 utility available (md5 or md5sum)"
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# Function to extract JSON values (with fallback if jq not available)
|
|
180
|
+
extract_json_value() {
|
|
181
|
+
local json="$1"
|
|
182
|
+
local key="$2"
|
|
183
|
+
|
|
184
|
+
if command -v jq >/dev/null 2>&1; then
|
|
185
|
+
echo "$json" | jq -r ".$key" 2>/dev/null || echo ""
|
|
186
|
+
else
|
|
187
|
+
# Fallback using grep and cut
|
|
188
|
+
echo "$json" | grep -o "\"$key\":[^,}]*" | cut -d: -f2- | sed 's/^"//;s/"$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | head -n1
|
|
189
|
+
fi
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Function to get error code meaning
|
|
193
|
+
get_error_code_meaning() {
|
|
194
|
+
local code="$1"
|
|
195
|
+
case "$code" in
|
|
196
|
+
"-11000086") echo "Legacy authentication required" ;;
|
|
197
|
+
"-11012022") echo "App version too low / API version mismatch" ;;
|
|
198
|
+
"-11260022") echo "Cross-region authentication error" ;;
|
|
199
|
+
"-10011") echo "Account does not exist" ;;
|
|
200
|
+
"-10013") echo "Account locked" ;;
|
|
201
|
+
"-10014") echo "Incorrect password" ;;
|
|
202
|
+
"-10015") echo "Account not activated" ;;
|
|
203
|
+
"-11000020") echo "Invalid token" ;;
|
|
204
|
+
"-11000021") echo "Token expired" ;;
|
|
205
|
+
"-11000041") echo "Too many requests" ;;
|
|
206
|
+
*) echo "" ;;
|
|
207
|
+
esac
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Function to explain error codes
|
|
211
|
+
explain_error_code() {
|
|
212
|
+
local code="$1"
|
|
213
|
+
local message="$2"
|
|
214
|
+
local meaning=$(get_error_code_meaning "$code")
|
|
215
|
+
|
|
216
|
+
if [[ -n "$meaning" ]]; then
|
|
217
|
+
echo -e "${RED}${FAILURE}${NC} Error Code: ${BOLD}$code${NC}"
|
|
218
|
+
echo -e " Meaning: $meaning"
|
|
219
|
+
[[ -n "$message" ]] && echo -e " Message: $message"
|
|
220
|
+
else
|
|
221
|
+
echo -e "${RED}${FAILURE}${NC} Error Code: ${BOLD}$code${NC}"
|
|
222
|
+
[[ -n "$message" ]] && echo -e " Message: $message"
|
|
223
|
+
echo -e " ${YELLOW}${WARNING}${NC} Unknown error code - may indicate API changes"
|
|
224
|
+
fi
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# Function to test network connectivity
|
|
228
|
+
test_connectivity() {
|
|
229
|
+
log "INFO" "Testing network connectivity..."
|
|
230
|
+
|
|
231
|
+
local endpoints=("smartapi.vesync.com" "smartapi.vesync.eu")
|
|
232
|
+
local connected=false
|
|
233
|
+
|
|
234
|
+
for endpoint in "${endpoints[@]}"; do
|
|
235
|
+
if curl -s --connect-timeout 5 --max-time 10 "https://$endpoint" >/dev/null 2>&1; then
|
|
236
|
+
log "SUCCESS" "Connected to $endpoint"
|
|
237
|
+
connected=true
|
|
238
|
+
else
|
|
239
|
+
log "WARN" "Cannot connect to $endpoint"
|
|
240
|
+
fi
|
|
241
|
+
done
|
|
242
|
+
|
|
243
|
+
if [[ "$connected" == "false" ]]; then
|
|
244
|
+
log "ERROR" "No connectivity to VeSync endpoints. Check your internet connection."
|
|
245
|
+
exit 1
|
|
246
|
+
fi
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# Function to test NEW authentication
|
|
250
|
+
test_new_authentication() {
|
|
251
|
+
local base_url="$1"
|
|
252
|
+
local region="$2"
|
|
253
|
+
|
|
254
|
+
echo -e "\n${YELLOW}${BOLD}Testing NEW Authentication (PR #340)...${NC}"
|
|
255
|
+
echo -e "${CYAN}${ARROW} Endpoint: $base_url${NC}"
|
|
256
|
+
echo -e "${CYAN}${ARROW} Region: $region${NC}"
|
|
257
|
+
|
|
258
|
+
local hashed_password=$(hash_password "$PASSWORD")
|
|
259
|
+
local timestamp=$(date +%s)
|
|
260
|
+
|
|
261
|
+
# Build JSON payload as a single line (this approach works reliably)
|
|
262
|
+
local auth_payload="{\"email\":\"$EMAIL\",\"method\":\"authByPWDOrOTM\",\"password\":\"$hashed_password\",\"acceptLanguage\":\"en\",\"accountID\":\"\",\"authProtocolType\":\"generic\",\"clientInfo\":\"SM N9005\",\"clientType\":\"vesyncApp\",\"clientVersion\":\"VeSync 5.6.60\",\"debugMode\":false,\"osInfo\":\"Android\",\"terminalId\":\"$TERMINAL_ID\",\"timeZone\":\"America/New_York\",\"token\":\"\",\"userCountryCode\":\"$region\",\"appID\":\"$APP_ID\",\"sourceAppID\":\"$APP_ID\",\"traceId\":\"APP${APP_ID}${timestamp}\"}"
|
|
263
|
+
|
|
264
|
+
log "DEBUG" "Sending auth request to: $base_url/globalPlatform/api/accountAuth/v1/authByPWDOrOTM"
|
|
265
|
+
|
|
266
|
+
local auth_response=$(curl -s --connect-timeout $DEFAULT_TIMEOUT --max-time $DEFAULT_TIMEOUT \
|
|
267
|
+
-X POST \
|
|
268
|
+
-H "Content-Type: application/json" \
|
|
269
|
+
-H "Accept: application/json" \
|
|
270
|
+
-H "User-Agent: VeSync/5.6.60 (Android; SM N9005)" \
|
|
271
|
+
-d "$auth_payload" \
|
|
272
|
+
"$base_url/globalPlatform/api/accountAuth/v1/authByPWDOrOTM" 2>/dev/null || echo '{"error":"network_error"}')
|
|
273
|
+
|
|
274
|
+
local region_lower=$(echo "$region" | tr '[:upper:]' '[:lower:]')
|
|
275
|
+
save_debug_response "$auth_response" "new_auth_${region_lower}_step1"
|
|
276
|
+
|
|
277
|
+
[[ "$VERBOSE" == "true" ]] && {
|
|
278
|
+
echo -e "\n${CYAN}Raw Response:${NC}"
|
|
279
|
+
if command -v jq >/dev/null 2>&1; then
|
|
280
|
+
echo "$auth_response" | jq '.'
|
|
281
|
+
else
|
|
282
|
+
echo "$auth_response"
|
|
283
|
+
fi
|
|
284
|
+
echo ""
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
# Check for network errors
|
|
288
|
+
if echo "$auth_response" | grep -q '"error":"network_error"' || [[ -z "$auth_response" ]]; then
|
|
289
|
+
echo -e "${RED}${FAILURE}${NC} Network error - cannot reach authentication endpoint"
|
|
290
|
+
return 1
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
local response_code=$(extract_json_value "$auth_response" "code")
|
|
294
|
+
local response_msg=$(extract_json_value "$auth_response" "msg")
|
|
295
|
+
local authorize_code=$(extract_json_value "$auth_response" "result.authorizeCode")
|
|
296
|
+
|
|
297
|
+
log "DEBUG" "Response code: $response_code"
|
|
298
|
+
log "DEBUG" "Response message: $response_msg"
|
|
299
|
+
|
|
300
|
+
case "$response_code" in
|
|
301
|
+
"0")
|
|
302
|
+
if [[ -n "$authorize_code" && "$authorize_code" != "null" ]]; then
|
|
303
|
+
echo -e "${GREEN}${SUCCESS}${NC} NEW Authentication Step 1 successful!"
|
|
304
|
+
echo -e " ${ARROW} Authorize Code: $(redact_pii "$authorize_code" "token")"
|
|
305
|
+
|
|
306
|
+
# Step 2: Exchange authorize code for token
|
|
307
|
+
echo -e "\n${CYAN}Testing Step 2: Token exchange...${NC}"
|
|
308
|
+
|
|
309
|
+
local timestamp2=$(date +%s)
|
|
310
|
+
local biz_token=$(extract_json_value "$auth_response" "result.bizToken")
|
|
311
|
+
local token_payload="{\"method\":\"loginByAuthorizeCode4Vesync\",\"authorizeCode\":\"$authorize_code\",\"acceptLanguage\":\"en\",\"accountID\":\"\",\"clientInfo\":\"SM N9005\",\"clientType\":\"vesyncApp\",\"clientVersion\":\"VeSync 5.6.60\",\"debugMode\":false,\"emailSubscriptions\":false,\"osInfo\":\"Android\",\"terminalId\":\"$TERMINAL_ID\",\"timeZone\":\"America/New_York\",\"token\":\"\",\"bizToken\":\"$biz_token\",\"regionChange\":\"\",\"userCountryCode\":\"$region\",\"traceId\":\"APP${APP_ID}${timestamp2}\"}"
|
|
312
|
+
|
|
313
|
+
local token_response=$(curl -s --connect-timeout $DEFAULT_TIMEOUT --max-time $DEFAULT_TIMEOUT \
|
|
314
|
+
-X POST \
|
|
315
|
+
-H "Content-Type: application/json" \
|
|
316
|
+
-H "Accept: application/json" \
|
|
317
|
+
-H "User-Agent: VeSync/5.6.60 (Android; SM N9005)" \
|
|
318
|
+
-d "$token_payload" \
|
|
319
|
+
"$base_url/user/api/accountManage/v1/loginByAuthorizeCode4Vesync" 2>/dev/null || echo '{"error":"network_error"}')
|
|
320
|
+
|
|
321
|
+
local region_lower2=$(echo "$region" | tr '[:upper:]' '[:lower:]')
|
|
322
|
+
save_debug_response "$token_response" "new_auth_${region_lower2}_step2"
|
|
323
|
+
|
|
324
|
+
local token_code=$(extract_json_value "$token_response" "code")
|
|
325
|
+
local token=$(extract_json_value "$token_response" "result.token")
|
|
326
|
+
local account_id=$(extract_json_value "$token_response" "result.accountID")
|
|
327
|
+
local country_code=$(extract_json_value "$token_response" "result.countryCode")
|
|
328
|
+
|
|
329
|
+
if [[ "$token_code" == "0" && -n "$token" && "$token" != "null" ]]; then
|
|
330
|
+
echo -e "${GREEN}${SUCCESS}${NC} NEW Authentication Step 2 successful!"
|
|
331
|
+
echo -e " ${ARROW} Token: $(redact_pii "$token" "token")"
|
|
332
|
+
echo -e " ${ARROW} Account ID: $(redact_pii "$account_id" "account_id")"
|
|
333
|
+
echo -e " ${ARROW} Country: ${country_code:-unknown}"
|
|
334
|
+
return 0
|
|
335
|
+
else
|
|
336
|
+
echo -e "${RED}${FAILURE}${NC} NEW Authentication Step 2 failed"
|
|
337
|
+
explain_error_code "$token_code" "$response_msg"
|
|
338
|
+
return 1
|
|
339
|
+
fi
|
|
340
|
+
else
|
|
341
|
+
echo -e "${RED}${FAILURE}${NC} NEW Authentication failed - no authorize code received"
|
|
342
|
+
return 1
|
|
343
|
+
fi
|
|
344
|
+
;;
|
|
345
|
+
"-11000086")
|
|
346
|
+
echo -e "${BLUE}${INFO}${NC} Legacy authentication required"
|
|
347
|
+
echo -e " ${ARROW} This account uses the traditional login flow"
|
|
348
|
+
return 2 # Special return code for legacy auth required
|
|
349
|
+
;;
|
|
350
|
+
"-11260022")
|
|
351
|
+
echo -e "${YELLOW}${WARNING}${NC} Cross-region authentication error"
|
|
352
|
+
echo -e " ${ARROW} Account may be registered in different region"
|
|
353
|
+
return 3 # Special return code for cross-region
|
|
354
|
+
;;
|
|
355
|
+
*)
|
|
356
|
+
explain_error_code "$response_code" "$response_msg"
|
|
357
|
+
return 1
|
|
358
|
+
;;
|
|
359
|
+
esac
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
# Function to test LEGACY authentication
|
|
363
|
+
test_legacy_authentication() {
|
|
364
|
+
local base_url="$1"
|
|
365
|
+
local region="$2"
|
|
366
|
+
|
|
367
|
+
echo -e "\n${YELLOW}${BOLD}Testing LEGACY Authentication...${NC}"
|
|
368
|
+
echo -e "${CYAN}${ARROW} Endpoint: $base_url${NC}"
|
|
369
|
+
echo -e "${CYAN}${ARROW} Region: $region${NC}"
|
|
370
|
+
|
|
371
|
+
local hashed_password=$(hash_password "$PASSWORD")
|
|
372
|
+
local timestamp=$(date +%s)
|
|
373
|
+
|
|
374
|
+
# Build legacy payload as single line
|
|
375
|
+
local legacy_payload="{\"email\":\"$EMAIL\",\"password\":\"$hashed_password\",\"appVersion\":\"2.8.6\",\"phoneBrand\":\"SM N9005\",\"phoneOS\":\"Android\",\"acceptLanguage\":\"en\",\"timeZone\":\"America/New_York\",\"method\":\"login\",\"token\":\"\",\"accountID\":\"\",\"devToken\":\"\",\"userType\":\"1\",\"traceId\":\"$timestamp\"}"
|
|
376
|
+
|
|
377
|
+
log "DEBUG" "Sending legacy auth request to: $base_url/cloud/v1/user/login"
|
|
378
|
+
|
|
379
|
+
local legacy_response=$(curl -s --connect-timeout $DEFAULT_TIMEOUT --max-time $DEFAULT_TIMEOUT \
|
|
380
|
+
-X POST \
|
|
381
|
+
-H "Content-Type: application/json" \
|
|
382
|
+
-H "Accept: application/json" \
|
|
383
|
+
-H "User-Agent: VeSync/2.8.6 (Android; SM N9005)" \
|
|
384
|
+
-d "$legacy_payload" \
|
|
385
|
+
"$base_url/cloud/v1/user/login" 2>/dev/null || echo '{"error":"network_error"}')
|
|
386
|
+
|
|
387
|
+
local region_lower=$(echo "$region" | tr '[:upper:]' '[:lower:]')
|
|
388
|
+
save_debug_response "$legacy_response" "legacy_auth_${region_lower}"
|
|
389
|
+
|
|
390
|
+
[[ "$VERBOSE" == "true" ]] && {
|
|
391
|
+
echo -e "\n${CYAN}Raw Response:${NC}"
|
|
392
|
+
if command -v jq >/dev/null 2>&1; then
|
|
393
|
+
echo "$legacy_response" | jq '.'
|
|
394
|
+
else
|
|
395
|
+
echo "$legacy_response"
|
|
396
|
+
fi
|
|
397
|
+
echo ""
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
# Check for network errors
|
|
401
|
+
if echo "$legacy_response" | grep -q '"error":"network_error"' || [[ -z "$legacy_response" ]]; then
|
|
402
|
+
echo -e "${RED}${FAILURE}${NC} Network error - cannot reach legacy authentication endpoint"
|
|
403
|
+
return 1
|
|
404
|
+
fi
|
|
405
|
+
|
|
406
|
+
local response_code=$(extract_json_value "$legacy_response" "code")
|
|
407
|
+
local response_msg=$(extract_json_value "$legacy_response" "msg")
|
|
408
|
+
local token=$(extract_json_value "$legacy_response" "result.token")
|
|
409
|
+
local account_id=$(extract_json_value "$legacy_response" "result.accountID")
|
|
410
|
+
local country_code=$(extract_json_value "$legacy_response" "result.countryCode")
|
|
411
|
+
|
|
412
|
+
log "DEBUG" "Legacy response code: $response_code"
|
|
413
|
+
log "DEBUG" "Legacy response message: $response_msg"
|
|
414
|
+
|
|
415
|
+
if [[ -n "$token" && "$token" != "null" ]]; then
|
|
416
|
+
echo -e "${GREEN}${SUCCESS}${NC} Legacy authentication successful!"
|
|
417
|
+
echo -e " ${ARROW} Token: $(redact_pii "$token" "token")"
|
|
418
|
+
echo -e " ${ARROW} Account ID: $(redact_pii "$account_id" "account_id")"
|
|
419
|
+
echo -e " ${ARROW} Country: ${country_code:-unknown}"
|
|
420
|
+
return 0
|
|
421
|
+
else
|
|
422
|
+
case "$response_code" in
|
|
423
|
+
"-11012022")
|
|
424
|
+
echo -e "${YELLOW}${WARNING}${NC} App version too low error"
|
|
425
|
+
echo -e " ${ARROW} This may indicate API changes requiring newer app version"
|
|
426
|
+
;;
|
|
427
|
+
"-11260022")
|
|
428
|
+
echo -e "${YELLOW}${WARNING}${NC} Cross-region authentication error"
|
|
429
|
+
echo -e " ${ARROW} Account may be registered in different region"
|
|
430
|
+
return 3 # Special return code for cross-region
|
|
431
|
+
;;
|
|
432
|
+
*)
|
|
433
|
+
explain_error_code "$response_code" "$response_msg"
|
|
434
|
+
;;
|
|
435
|
+
esac
|
|
436
|
+
return 1
|
|
437
|
+
fi
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
# Function to test authentication for multiple regions
|
|
441
|
+
test_regional_authentication() {
|
|
442
|
+
local auth_type="$1" # "new" or "legacy"
|
|
443
|
+
local regions=("US" "EU")
|
|
444
|
+
local endpoints=("https://smartapi.vesync.com" "https://smartapi.vesync.eu")
|
|
445
|
+
local successful_regions=""
|
|
446
|
+
local failed_regions=""
|
|
447
|
+
local cross_region_errors=""
|
|
448
|
+
|
|
449
|
+
# Initialize global variables to store individual region results
|
|
450
|
+
if [[ "$auth_type" == "new" ]]; then
|
|
451
|
+
NEW_AUTH_US_RESULT=1
|
|
452
|
+
NEW_AUTH_EU_RESULT=1
|
|
453
|
+
else
|
|
454
|
+
LEGACY_AUTH_US_RESULT=1
|
|
455
|
+
LEGACY_AUTH_EU_RESULT=1
|
|
456
|
+
fi
|
|
457
|
+
|
|
458
|
+
for i in "${!regions[@]}"; do
|
|
459
|
+
local region="${regions[i]}"
|
|
460
|
+
local base_url="${endpoints[i]}"
|
|
461
|
+
local result=0
|
|
462
|
+
|
|
463
|
+
echo -e "\n${CYAN}${ARROW} Testing $auth_type authentication for $region region...${NC}"
|
|
464
|
+
|
|
465
|
+
# Test authentication with error handling to prevent script exit
|
|
466
|
+
if [[ "$auth_type" == "new" ]]; then
|
|
467
|
+
test_new_authentication "$base_url" "$region" || result=$?
|
|
468
|
+
if [[ "$region" == "US" ]]; then
|
|
469
|
+
NEW_AUTH_US_RESULT=$result
|
|
470
|
+
else
|
|
471
|
+
NEW_AUTH_EU_RESULT=$result
|
|
472
|
+
fi
|
|
473
|
+
else
|
|
474
|
+
test_legacy_authentication "$base_url" "$region" || result=$?
|
|
475
|
+
if [[ "$region" == "US" ]]; then
|
|
476
|
+
LEGACY_AUTH_US_RESULT=$result
|
|
477
|
+
else
|
|
478
|
+
LEGACY_AUTH_EU_RESULT=$result
|
|
479
|
+
fi
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
# Display immediate result for this region
|
|
483
|
+
case $result in
|
|
484
|
+
0)
|
|
485
|
+
echo -e "${GREEN}${SUCCESS}${NC} $auth_type authentication successful for $region region"
|
|
486
|
+
successful_regions="$successful_regions $region"
|
|
487
|
+
;;
|
|
488
|
+
2)
|
|
489
|
+
echo -e "${BLUE}${INFO}${NC} $auth_type authentication not supported for $region region (legacy required)"
|
|
490
|
+
failed_regions="$failed_regions $region"
|
|
491
|
+
;;
|
|
492
|
+
3)
|
|
493
|
+
echo -e "${YELLOW}${WARNING}${NC} Cross-region authentication error for $region region"
|
|
494
|
+
cross_region_errors="$cross_region_errors $region"
|
|
495
|
+
;;
|
|
496
|
+
*)
|
|
497
|
+
echo -e "${RED}${FAILURE}${NC} $auth_type authentication failed for $region region"
|
|
498
|
+
failed_regions="$failed_regions $region"
|
|
499
|
+
;;
|
|
500
|
+
esac
|
|
501
|
+
done
|
|
502
|
+
|
|
503
|
+
# Report overall regional results
|
|
504
|
+
if [[ -n "$successful_regions" ]]; then
|
|
505
|
+
echo -e "\n${GREEN}${SUCCESS}${NC} $auth_type authentication successful in regions:$successful_regions"
|
|
506
|
+
return 0
|
|
507
|
+
elif [[ -n "$cross_region_errors" ]]; then
|
|
508
|
+
echo -e "\n${YELLOW}${WARNING}${NC} Cross-region errors in:$cross_region_errors"
|
|
509
|
+
return 3
|
|
510
|
+
else
|
|
511
|
+
echo -e "\n${RED}${FAILURE}${NC} $auth_type authentication failed in all tested regions"
|
|
512
|
+
return 1
|
|
513
|
+
fi
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
# Function to guess region from email domain
|
|
517
|
+
guess_region_from_email() {
|
|
518
|
+
local email="$1"
|
|
519
|
+
local domain="${email##*@}"
|
|
520
|
+
|
|
521
|
+
# European domains
|
|
522
|
+
local eu_domains=(".de" ".fr" ".it" ".es" ".uk" ".co.uk" ".nl" ".be" ".at" ".ch" ".se" ".no" ".dk" ".fi")
|
|
523
|
+
|
|
524
|
+
for eu_domain in "${eu_domains[@]}"; do
|
|
525
|
+
if [[ "$domain" == *"$eu_domain" ]]; then
|
|
526
|
+
echo "EU"
|
|
527
|
+
return
|
|
528
|
+
fi
|
|
529
|
+
done
|
|
530
|
+
|
|
531
|
+
echo "US"
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
# Function to print section header
|
|
535
|
+
print_section() {
|
|
536
|
+
local title="$1"
|
|
537
|
+
local width=80
|
|
538
|
+
local padding=$((($width - ${#title} - 2) / 2))
|
|
539
|
+
|
|
540
|
+
echo ""
|
|
541
|
+
echo -e "${BLUE}$(printf '═%.0s' $(seq 1 $width))${NC}"
|
|
542
|
+
echo -e "${BLUE}$(printf '═%.0s' $(seq 1 $padding)) ${BOLD}$title${NC}${BLUE} $(printf '═%.0s' $(seq 1 $padding))${NC}"
|
|
543
|
+
echo -e "${BLUE}$(printf '═%.0s' $(seq 1 $width))${NC}"
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
# Function to get result status display
|
|
547
|
+
get_result_status() {
|
|
548
|
+
local result="$1"
|
|
549
|
+
case "$result" in
|
|
550
|
+
0) echo -e "${GREEN}${SUCCESS} Success${NC}" ;;
|
|
551
|
+
2) echo -e "${BLUE}${INFO} Legacy Required${NC}" ;;
|
|
552
|
+
3) echo -e "${YELLOW}${WARNING} Region Mismatch${NC}" ;;
|
|
553
|
+
*) echo -e "${RED}${FAILURE} Failed${NC}" ;;
|
|
554
|
+
esac
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
# Function to print final summary and recommendations
|
|
558
|
+
print_final_summary() {
|
|
559
|
+
local new_auth_result="$1"
|
|
560
|
+
local legacy_auth_result="$2"
|
|
561
|
+
local detected_region="$3"
|
|
562
|
+
|
|
563
|
+
print_section "FINAL SUMMARY & RECOMMENDATIONS"
|
|
564
|
+
|
|
565
|
+
echo -e "${BOLD}Account Information:${NC}"
|
|
566
|
+
echo -e " Email: $(redact_pii "$EMAIL" "email")"
|
|
567
|
+
echo -e " Detected Region: $detected_region"
|
|
568
|
+
echo ""
|
|
569
|
+
|
|
570
|
+
echo -e "${BOLD}Authentication Test Results:${NC}"
|
|
571
|
+
echo -e " NEW Auth US: $(get_result_status "${NEW_AUTH_US_RESULT:-1}")"
|
|
572
|
+
echo -e " NEW Auth EU: $(get_result_status "${NEW_AUTH_EU_RESULT:-1}")"
|
|
573
|
+
echo -e " LEGACY Auth US: $(get_result_status "${LEGACY_AUTH_US_RESULT:-1}")"
|
|
574
|
+
echo -e " LEGACY Auth EU: $(get_result_status "${LEGACY_AUTH_EU_RESULT:-1}")"
|
|
575
|
+
echo ""
|
|
576
|
+
|
|
577
|
+
# Determine which method(s) work
|
|
578
|
+
local working_methods=""
|
|
579
|
+
if [[ "${NEW_AUTH_US_RESULT:-1}" == "0" || "${NEW_AUTH_EU_RESULT:-1}" == "0" ]]; then
|
|
580
|
+
working_methods="NEW Authentication"
|
|
581
|
+
fi
|
|
582
|
+
if [[ "${LEGACY_AUTH_US_RESULT:-1}" == "0" || "${LEGACY_AUTH_EU_RESULT:-1}" == "0" ]]; then
|
|
583
|
+
if [[ -n "$working_methods" ]]; then
|
|
584
|
+
working_methods="$working_methods, LEGACY Authentication"
|
|
585
|
+
else
|
|
586
|
+
working_methods="LEGACY Authentication"
|
|
587
|
+
fi
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
if [[ -n "$working_methods" ]]; then
|
|
591
|
+
echo -e "${BOLD}${GREEN}FINAL VERDICT: $working_methods works for this account${NC}"
|
|
592
|
+
else
|
|
593
|
+
echo -e "${BOLD}${RED}FINAL VERDICT: No authentication methods worked${NC}"
|
|
594
|
+
fi
|
|
595
|
+
echo ""
|
|
596
|
+
|
|
597
|
+
# Determine authentication type based on individual region results
|
|
598
|
+
local new_auth_works=false
|
|
599
|
+
local legacy_auth_works=false
|
|
600
|
+
|
|
601
|
+
# Check if NEW auth worked in any region
|
|
602
|
+
if [[ "${NEW_AUTH_US_RESULT:-1}" == "0" || "${NEW_AUTH_EU_RESULT:-1}" == "0" ]]; then
|
|
603
|
+
new_auth_works=true
|
|
604
|
+
fi
|
|
605
|
+
|
|
606
|
+
# Check if LEGACY auth worked in any region
|
|
607
|
+
if [[ "${LEGACY_AUTH_US_RESULT:-1}" == "0" || "${LEGACY_AUTH_EU_RESULT:-1}" == "0" ]]; then
|
|
608
|
+
legacy_auth_works=true
|
|
609
|
+
fi
|
|
610
|
+
|
|
611
|
+
# Determine authentication type
|
|
612
|
+
if [[ "$new_auth_works" == "true" ]]; then
|
|
613
|
+
echo -e " ${GREEN}${SUCCESS}${NC} ${BOLD}Authentication Type: NEW (PR #340)${NC}"
|
|
614
|
+
echo -e " ${ARROW} Your account supports the new two-step authentication flow"
|
|
615
|
+
echo -e " ${ARROW} Compatible with pyvesync PR #340"
|
|
616
|
+
echo -e " ${ARROW} Uses VeSync app version 5.6.60 or higher"
|
|
617
|
+
|
|
618
|
+
elif [[ "$legacy_auth_works" == "true" ]]; then
|
|
619
|
+
# Check if new auth explicitly required legacy (returned code 2)
|
|
620
|
+
if [[ "${NEW_AUTH_US_RESULT:-1}" == "2" || "${NEW_AUTH_EU_RESULT:-1}" == "2" ]]; then
|
|
621
|
+
echo -e " ${BLUE}${INFO}${NC} ${BOLD}Authentication Type: LEGACY ONLY${NC}"
|
|
622
|
+
echo -e " ${ARROW} Account explicitly requires legacy authentication"
|
|
623
|
+
else
|
|
624
|
+
echo -e " ${YELLOW}${WARNING}${NC} ${BOLD}Authentication Type: LEGACY (Fallback)${NC}"
|
|
625
|
+
echo -e " ${ARROW} New auth failed, but legacy auth works"
|
|
626
|
+
fi
|
|
627
|
+
echo -e " ${ARROW} Uses traditional single-step login flow"
|
|
628
|
+
echo -e " ${ARROW} Compatible with existing pyvesync implementations"
|
|
629
|
+
echo -e " ${ARROW} Uses VeSync app version 2.8.6"
|
|
630
|
+
|
|
631
|
+
elif [[ "${NEW_AUTH_US_RESULT:-1}" == "3" || "${NEW_AUTH_EU_RESULT:-1}" == "3" || "${LEGACY_AUTH_US_RESULT:-1}" == "3" || "${LEGACY_AUTH_EU_RESULT:-1}" == "3" ]]; then
|
|
632
|
+
echo -e " ${YELLOW}${WARNING}${NC} ${BOLD}Authentication Type: REGION MISMATCH${NC}"
|
|
633
|
+
echo -e " ${ARROW} Cross-region authentication errors detected"
|
|
634
|
+
echo -e " ${ARROW} Try different regional endpoints:"
|
|
635
|
+
echo -e " - US: https://smartapi.vesync.com"
|
|
636
|
+
echo -e " - EU: https://smartapi.vesync.eu"
|
|
637
|
+
|
|
638
|
+
else
|
|
639
|
+
echo -e " ${RED}${FAILURE}${NC} ${BOLD}Authentication Type: UNDETERMINED${NC}"
|
|
640
|
+
echo -e " ${ARROW} Both new and legacy authentication failed"
|
|
641
|
+
echo -e " ${ARROW} Verify your email and password are correct"
|
|
642
|
+
echo -e " ${ARROW} Check if your account is locked or suspended"
|
|
643
|
+
fi
|
|
644
|
+
|
|
645
|
+
# Additional troubleshooting info
|
|
646
|
+
echo ""
|
|
647
|
+
echo -e "${BOLD}Troubleshooting:${NC}"
|
|
648
|
+
if [[ "$DEBUG" == "true" ]]; then
|
|
649
|
+
echo -e " • Debug files saved to: ${DEBUG_DIR:-/tmp/vesync-auth-debug-*}"
|
|
650
|
+
fi
|
|
651
|
+
echo -e " • Run with -v flag for verbose API responses"
|
|
652
|
+
echo -e " • Run with -d flag to save debug files"
|
|
653
|
+
echo -e " • Check network connectivity to VeSync endpoints"
|
|
654
|
+
|
|
655
|
+
print_section "SCRIPT COMPLETED"
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
# Main execution function
|
|
659
|
+
main() {
|
|
660
|
+
# Parse command line arguments
|
|
661
|
+
while [[ $# -gt 0 ]]; do
|
|
662
|
+
case $1 in
|
|
663
|
+
-v|--verbose)
|
|
664
|
+
VERBOSE=true
|
|
665
|
+
shift
|
|
666
|
+
;;
|
|
667
|
+
-d|--debug)
|
|
668
|
+
DEBUG=true
|
|
669
|
+
VERBOSE=true # Debug implies verbose
|
|
670
|
+
shift
|
|
671
|
+
;;
|
|
672
|
+
-h|--help)
|
|
673
|
+
show_usage
|
|
674
|
+
exit 0
|
|
675
|
+
;;
|
|
676
|
+
-*)
|
|
677
|
+
echo "Unknown option: $1" >&2
|
|
678
|
+
show_usage
|
|
679
|
+
exit 1
|
|
680
|
+
;;
|
|
681
|
+
*)
|
|
682
|
+
if [[ -z "$EMAIL" ]]; then
|
|
683
|
+
EMAIL="$1"
|
|
684
|
+
elif [[ -z "$PASSWORD" ]]; then
|
|
685
|
+
PASSWORD="$1"
|
|
686
|
+
else
|
|
687
|
+
echo "Too many arguments" >&2
|
|
688
|
+
show_usage
|
|
689
|
+
exit 1
|
|
690
|
+
fi
|
|
691
|
+
shift
|
|
692
|
+
;;
|
|
693
|
+
esac
|
|
694
|
+
done
|
|
695
|
+
|
|
696
|
+
# Load environment variables if available
|
|
697
|
+
if [[ -f .env ]]; then
|
|
698
|
+
set -a
|
|
699
|
+
source .env
|
|
700
|
+
set +a
|
|
701
|
+
fi
|
|
702
|
+
|
|
703
|
+
# Use environment variables if arguments not provided
|
|
704
|
+
EMAIL="${EMAIL:-${VESYNC_USERNAME:-}}"
|
|
705
|
+
PASSWORD="${PASSWORD:-${VESYNC_PASSWORD:-}}"
|
|
706
|
+
|
|
707
|
+
# Validate required parameters
|
|
708
|
+
if [[ -z "$EMAIL" || -z "$PASSWORD" ]]; then
|
|
709
|
+
echo -e "${RED}ERROR: Email and password are required${NC}" >&2
|
|
710
|
+
echo "Usage: $0 [email] [password]"
|
|
711
|
+
echo "Or set VESYNC_USERNAME and VESYNC_PASSWORD in .env"
|
|
712
|
+
exit 1
|
|
713
|
+
fi
|
|
714
|
+
|
|
715
|
+
# Setup debug environment
|
|
716
|
+
setup_debug_dir
|
|
717
|
+
|
|
718
|
+
# Generate unique identifiers
|
|
719
|
+
APP_ID=$(generate_app_id)
|
|
720
|
+
TERMINAL_ID=$(generate_terminal_id)
|
|
721
|
+
|
|
722
|
+
# Print header
|
|
723
|
+
print_section "$SCRIPT_NAME v$SCRIPT_VERSION"
|
|
724
|
+
|
|
725
|
+
echo -e "${BOLD}Configuration:${NC}"
|
|
726
|
+
echo -e " Account: $(redact_pii "$EMAIL" "email")"
|
|
727
|
+
echo -e " App ID: $APP_ID"
|
|
728
|
+
echo -e " Terminal ID: $TERMINAL_ID"
|
|
729
|
+
echo -e " Verbose Mode: $VERBOSE"
|
|
730
|
+
echo -e " Debug Mode: $DEBUG"
|
|
731
|
+
[[ "$DEBUG" == "true" ]] && echo -e " Debug Directory: $DEBUG_DIR"
|
|
732
|
+
|
|
733
|
+
# Test connectivity first
|
|
734
|
+
test_connectivity
|
|
735
|
+
|
|
736
|
+
# Guess user's region
|
|
737
|
+
local guessed_region=$(guess_region_from_email "$EMAIL")
|
|
738
|
+
log "INFO" "Detected likely region: $guessed_region"
|
|
739
|
+
|
|
740
|
+
# Test NEW authentication flow first
|
|
741
|
+
print_section "NEW AUTHENTICATION TESTING (PR #340)"
|
|
742
|
+
echo -e "${YELLOW}${INFO}${NC} Testing new authentication flow across all regions..."
|
|
743
|
+
local new_auth_result=1
|
|
744
|
+
test_regional_authentication "new" || new_auth_result=$?
|
|
745
|
+
|
|
746
|
+
echo -e "\n${CYAN}${ARROW} NEW authentication testing completed. Continuing to LEGACY testing...${NC}"
|
|
747
|
+
|
|
748
|
+
# Test LEGACY authentication flow
|
|
749
|
+
print_section "LEGACY AUTHENTICATION TESTING"
|
|
750
|
+
echo -e "${YELLOW}${INFO}${NC} Testing legacy authentication flow across all regions..."
|
|
751
|
+
local legacy_auth_result=1
|
|
752
|
+
test_regional_authentication "legacy" || legacy_auth_result=$?
|
|
753
|
+
|
|
754
|
+
echo -e "\n${CYAN}${ARROW} LEGACY authentication testing completed. Generating final summary...${NC}"
|
|
755
|
+
|
|
756
|
+
# Print final summary and recommendations
|
|
757
|
+
print_final_summary "$new_auth_result" "$legacy_auth_result" "$guessed_region"
|
|
758
|
+
|
|
759
|
+
# Exit with appropriate code based on individual results
|
|
760
|
+
if [[ "${NEW_AUTH_US_RESULT:-1}" == "0" || "${NEW_AUTH_EU_RESULT:-1}" == "0" || "${LEGACY_AUTH_US_RESULT:-1}" == "0" || "${LEGACY_AUTH_EU_RESULT:-1}" == "0" ]]; then
|
|
761
|
+
exit 0 # Success
|
|
762
|
+
elif [[ "${NEW_AUTH_US_RESULT:-1}" == "3" || "${NEW_AUTH_EU_RESULT:-1}" == "3" || "${LEGACY_AUTH_US_RESULT:-1}" == "3" || "${LEGACY_AUTH_EU_RESULT:-1}" == "3" ]]; then
|
|
763
|
+
exit 3 # Region mismatch
|
|
764
|
+
else
|
|
765
|
+
exit 1 # Authentication failed
|
|
766
|
+
fi
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
# Run main function with all arguments
|
|
770
|
+
main "$@"
|