vnai 2.1.9__py3-none-any.whl → 2.3.7__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.
vnai/__init__.py CHANGED
@@ -5,23 +5,16 @@ import time
5
5
  import threading
6
6
  import functools
7
7
  from datetime import datetime
8
- from vnai.beam.quota import guardian, optimize
9
- from vnai.beam.metrics import collector, capture
10
- from vnai.beam.pulse import monitor
11
- from vnai.flow.relay import conduit
12
- from vnai.flow.queue import buffer
13
- from vnai.scope.profile import inspector
14
- from vnai.scope.state import tracker, record
15
- import vnai.scope.promo
16
- from vnai.scope.promo import present
8
+ from typing import Optional
9
+ import pandas as pd
17
10
  TC_VAR ="ACCEPT_TC"
18
11
  TC_VAL ="tôi đồng ý"
19
12
  TC_PATH = pathlib.Path.home() /".vnstock" /"id" /"terms_agreement.txt"
20
13
  TERMS_AND_CONDITIONS ="""
21
14
  Khi tiếp tục sử dụng Vnstock, bạn xác nhận rằng bạn đã đọc, hiểu và đồng ý với Chính sách quyền riêng tư và Điều khoản, điều kiện về giấy phép sử dụng Vnstock.
22
15
  Chi tiết:
23
- - Giấy phép sử dụng phần mềm: https://vnstocks.com/docs/tai-lieu/giay-phep-su-dung
24
- - Chính sách quyền riêng tư: https://vnstocks.com/docs/tai-lieu/chinh-sach-quyen-rieng-tu
16
+ - Giấy phép sử dụng phần mềm: https://vnstocks.com/onboard/giay-phep-su-dung
17
+ - Chính sách quyền riêng tư: https://vnstocks.com/onboard/chinh-sach-quyen-rieng-tu
25
18
  """
26
19
 
27
20
  class Core:
@@ -45,12 +38,30 @@ class Core:
45
38
  self._accept_terms()
46
39
  from vnai.scope.profile import inspector
47
40
  inspector.setup_vnstock_environment()
41
+ try:
42
+ from vnai.scope.device import device_registry
43
+ vnstock_version = getattr(__import__('vnstock'),
44
+ '__version__','0.0.1')
45
+ if device_registry.needs_reregistration(vnstock_version):
46
+ system_info = inspector.examine()
47
+ device_registry.register(system_info, vnstock_version)
48
+ self.system_info = system_info
49
+ else:
50
+ self.system_info = device_registry.get_registry()
51
+ except Exception as e:
52
+ import logging
53
+ logger = logging.getLogger(__name__)
54
+ msg =f"Device registration failed: {e}. Using fallback."
55
+ logger.warning(msg)
56
+ self.system_info = inspector.examine()
48
57
  from vnai.scope.promo import ContentManager
49
58
  manager = ContentManager()
50
- if manager.is_paid_user is False and getattr(manager,'license_checked', False):
59
+ if manager.is_paid_user is not True:
60
+ from vnai.scope.promo import present
51
61
  present()
62
+ from vnai.scope.state import record
52
63
  record("initialization", {"timestamp": datetime.now().isoformat()})
53
- self.system_info = inspector.examine()
64
+ from vnai.flow.relay import conduit
54
65
  conduit.queue({
55
66
  "type":"system_info",
56
67
  "data": {
@@ -59,25 +70,25 @@ class Core:
59
70
  }
60
71
  }, priority="high")
61
72
  self.initialized = True
73
+ _trigger_patching_after_init()
62
74
  return True
63
75
 
64
76
  def _check_terms(self):
65
77
  return os.path.exists(self.terms_file_path)
66
78
 
67
79
  def _accept_terms(self):
80
+ from vnai.scope.profile import inspector
68
81
  system_info = inspector.examine()
69
82
  if TC_VAR in os.environ and os.environ[TC_VAR] == TC_VAL:
70
- response = TC_VAL
83
+ os.environ[TC_VAR] = TC_VAL
71
84
  else:
72
- response = TC_VAL
73
85
  os.environ[TC_VAR] = TC_VAL
74
86
  now = datetime.now()
87
+ machine_id = system_info['machine_id']
75
88
  signed_agreement = (
76
- f"Người dùng có mã nhận dạng {system_info['machine_id']} đã chấp nhận "
77
- f"điều khoản & điều kiện sử dụng Vnstock lúc {now}\n"
78
- f"---\n\n"
79
- f"THÔNG TIN THIẾT BỊ: {json.dumps(system_info, indent=2)}\n\n"
80
- f"Đính kèm bản sao nội dung bạn đã đọc, hiểu rõ và đồng ý dưới đây:\n"
89
+ f"Người dùng có mã nhận dạng {machine_id} "
90
+ f"đã chấp nhận điều khoản & điều kiện sử dụng Vnstock "
91
+ f"lúc {now.isoformat()}\n\n"
81
92
  f"{TERMS_AND_CONDITIONS}"
82
93
  )
83
94
  with open(self.terms_file_path,"w", encoding="utf-8") as f:
@@ -86,13 +97,15 @@ f"{TERMS_AND_CONDITIONS}"
86
97
  env_data = {
87
98
  "accepted_agreement": True,
88
99
  "timestamp": now.isoformat(),
89
- "machine_id": system_info['machine_id']
100
+ "machine_id": machine_id
90
101
  }
91
102
  with open(env_file,"w") as f:
92
103
  json.dump(env_data, f)
93
104
  return True
94
105
 
95
106
  def status(self):
107
+ from vnai.beam.pulse import monitor
108
+ from vnai.scope.state import tracker
96
109
  return {
97
110
  "initialized": self.initialized,
98
111
  "health": monitor.report(),
@@ -102,32 +115,81 @@ f"{TERMS_AND_CONDITIONS}"
102
115
  def configure_privacy(self, level="standard"):
103
116
  from vnai.scope.state import tracker
104
117
  return tracker.setup_privacy(level)
105
- core = Core()
118
+ _core_instance = None
119
+ _core_lock = threading.Lock()
120
+
121
+ def _get_core():
122
+ global _core_instance
123
+ if _core_instance is None:
124
+ with _core_lock:
125
+ if _core_instance is None:
126
+ _core_instance = Core()
127
+ return _core_instance
106
128
 
107
129
  def tc_init():
108
- return core.initialize()
130
+ return _get_core().initialize()
109
131
 
110
132
  def setup():
111
- return core.initialize()
133
+ return _get_core().initialize()
112
134
 
113
135
  def optimize_execution(resource_type="default"):
114
- return optimize(resource_type)
136
+ def decorator(func):
137
+ _optimized_func = [None]
138
+ @functools.wraps(func)
139
+
140
+ def wrapper(*args, **kwargs):
141
+ if _optimized_func[0] is None:
142
+ from vnai.beam.quota import optimize
143
+ actual_decorator = optimize(resource_type)
144
+ _optimized_func[0] = actual_decorator(func)
145
+ return _optimized_func[0](*args, **kwargs)
146
+ return wrapper
147
+ return decorator
115
148
 
116
149
  def agg_execution(resource_type="default"):
117
- return optimize(resource_type, ad_cooldown=1500, content_trigger_threshold=100000)
150
+ def decorator(func):
151
+ _optimized_func = [None]
152
+ @functools.wraps(func)
153
+
154
+ def wrapper(*args, **kwargs):
155
+ if _optimized_func[0] is None:
156
+ from vnai.beam.quota import optimize
157
+ actual_decorator = optimize(resource_type, ad_cooldown=1500,
158
+ content_trigger_threshold=100000)
159
+ _optimized_func[0] = actual_decorator(func)
160
+ return _optimized_func[0](*args, **kwargs)
161
+ return wrapper
162
+ return decorator
118
163
 
119
164
  def measure_performance(module_type="function"):
120
- return capture(module_type)
165
+ def decorator(func):
166
+ _captured_func = [None]
167
+ @functools.wraps(func)
168
+
169
+ def wrapper(*args, **kwargs):
170
+ if _captured_func[0] is None:
171
+ from vnai.beam.metrics import capture
172
+ actual_decorator = capture(module_type)
173
+ _captured_func[0] = actual_decorator(func)
174
+ return _captured_func[0](*args, **kwargs)
175
+ return wrapper
176
+ return decorator
121
177
 
122
178
  def accept_license_terms(terms_text=None):
123
179
  if terms_text is None:
124
180
  terms_text = TERMS_AND_CONDITIONS
181
+ from vnai.scope.profile import inspector
125
182
  system_info = inspector.examine()
126
- terms_file = pathlib.Path.home() /".vnstock" /"id" /"terms_agreement.txt"
127
- os.makedirs(os.path.dirname(terms_file), exist_ok=True)
128
- with open(terms_file,"w", encoding="utf-8") as f:
129
- f.write(f"Terms accepted at {datetime.now().isoformat()}\n")
130
- f.write(f"System: {json.dumps(system_info)}\n\n")
183
+ terms_file_path = (
184
+ pathlib.Path.home() /".vnstock" /"id" /
185
+ "terms_agreement.txt"
186
+ )
187
+ os.makedirs(os.path.dirname(terms_file_path), exist_ok=True)
188
+ now = datetime.now()
189
+ machine_id = system_info['machine_id']
190
+ with open(terms_file_path,"w", encoding="utf-8") as f:
191
+ f.write(f"Người dùng có mã nhận dạng {machine_id} "
192
+ f"đã chấp nhận lúc {now.isoformat()}\n\n")
131
193
  f.write(terms_text)
132
194
  return True
133
195
 
@@ -148,6 +210,13 @@ def accept_vnstock_terms():
148
210
  try:
149
211
  with open(env_file,"w") as f:
150
212
  json.dump(env_data, f)
213
+ terms_file = id_dir /"terms_agreement.txt"
214
+ now = datetime.now()
215
+ machine_id = system_info['machine_id']
216
+ with open(terms_file,"w", encoding="utf-8") as f:
217
+ f.write(f"Người dùng có mã nhận dạng {machine_id} "
218
+ f"đã chấp nhận lúc {now.isoformat()}\n\n")
219
+ f.write(TERMS_AND_CONDITIONS)
151
220
  print("Vnstock terms accepted successfully.")
152
221
  return True
153
222
  except Exception as e:
@@ -164,4 +233,95 @@ def check_commercial_usage():
164
233
 
165
234
  def authenticate_for_persistence():
166
235
  from vnai.scope.profile import inspector
167
- return inspector.get_or_create_user_id()
236
+ return inspector.get_or_create_user_id()
237
+
238
+ def get_user_tier():
239
+ try:
240
+ from vnai.beam.auth import authenticator
241
+ return authenticator.get_tier_info()
242
+ except Exception as e:
243
+ return {
244
+ "tier":"guest",
245
+ "description":"Khách (không có API key)",
246
+ "limits": {"per_minute": 20,"per_hour": 1200},
247
+ "error": str(e)
248
+ }
249
+
250
+ def refresh_tier_cache():
251
+ try:
252
+ from vnai.beam.auth import authenticator
253
+ authenticator.get_tier(force_refresh=True)
254
+ return True
255
+ except Exception:
256
+ return False
257
+
258
+ def setup_api_key(api_key):
259
+ from vnai.beam.auth import authenticator
260
+ return authenticator.setup_api_key(api_key)
261
+
262
+ def get_api_key():
263
+ from vnai.beam.auth import authenticator
264
+ return authenticator.get_api_key()
265
+
266
+ def remove_api_key():
267
+ from vnai.beam.auth import authenticator
268
+ return authenticator.remove_api_key()
269
+
270
+ def check_api_key_status():
271
+ from vnai.beam.auth import authenticator
272
+ return authenticator.check_api_key_status()
273
+
274
+ def print_api_key_help():
275
+ from vnai.beam.auth import authenticator
276
+ return authenticator.print_help()
277
+
278
+ def get_quota_status(api_key):
279
+ from vnai.beam.quota_endpoint import quota_endpoint
280
+ return quota_endpoint.get_quota_status(api_key)
281
+
282
+ def get_tier_info():
283
+ from vnai.beam.quota_endpoint import quota_endpoint
284
+ return quota_endpoint.get_tier_info()
285
+
286
+ def check_quota_available(api_key):
287
+ from vnai.beam.quota_endpoint import quota_endpoint
288
+ return quota_endpoint.check_quota(api_key)
289
+
290
+ def get_quota_metadata(api_key):
291
+ from vnai.beam.quota_endpoint import quota_endpoint
292
+ return quota_endpoint.get_metadata(api_key)
293
+
294
+ def record_api_usage(api_key, amount=1):
295
+ from vnai.beam.quota_endpoint import quota_endpoint
296
+ return quota_endpoint.record_usage(api_key, amount)
297
+
298
+ def balance_sheet(symbol: str, source: str ='vci', period: str ='year',
299
+ lang: Optional[str] ='en', show_log: bool = False) -> pd.DataFrame:
300
+ _ensure_patches_applied()
301
+ from vnai.beam.fundamental import balance_sheet as get_balance_sheet
302
+ return get_balance_sheet(symbol, source=source, period=period, lang=lang, show_log=show_log)
303
+
304
+ def income_statement(symbol: str, source: str ='vci', period: str ='year',
305
+ lang: Optional[str] ='en', show_log: bool = False) -> pd.DataFrame:
306
+ _ensure_patches_applied()
307
+ from vnai.beam.fundamental import income_statement as get_income_statement
308
+ return get_income_statement(symbol, source=source, period=period, lang=lang, show_log=show_log)
309
+
310
+ def cash_flow(symbol, source='vci', period='year', lang='en', show_log=False):
311
+ _ensure_patches_applied()
312
+ from vnai.beam.fundamental import cash_flow as get_cash_flow
313
+ return get_cash_flow(symbol, source=source, period=period, lang=lang, show_log=show_log)
314
+ _patches_initialized = False
315
+
316
+ def _ensure_patches_applied():
317
+ global _patches_initialized
318
+ if not _patches_initialized:
319
+ try:
320
+ from vnai.beam.patching import apply_all_patches
321
+ apply_all_patches()
322
+ _patches_initialized = True
323
+ except Exception:
324
+ _patches_initialized = True
325
+
326
+ def _trigger_patching_after_init():
327
+ _ensure_patches_applied()
vnai/beam/__init__.py CHANGED
@@ -1,3 +1,26 @@
1
- from vnai.beam.quota import guardian, optimize
2
- from vnai.beam.metrics import collector, capture
3
- from vnai.beam.pulse import monitor
1
+ def __getattr__(name):
2
+ if name =='guardian':
3
+ from vnai.beam.quota import guardian
4
+ return guardian
5
+ elif name =='optimize':
6
+ from vnai.beam.quota import optimize
7
+ return optimize
8
+ elif name =='collector':
9
+ from vnai.beam.metrics import collector
10
+ return collector
11
+ elif name =='capture':
12
+ from vnai.beam.metrics import capture
13
+ return capture
14
+ elif name =='monitor':
15
+ from vnai.beam.pulse import monitor
16
+ return monitor
17
+ elif name =='get_auth_state_manager':
18
+ from vnai.beam.auth import get_auth_state_manager
19
+ return get_auth_state_manager
20
+ elif name =='AuthStateManager':
21
+ from vnai.beam.auth import AuthStateManager
22
+ return AuthStateManager
23
+ elif name =='authenticator':
24
+ from vnai.beam.auth import authenticator
25
+ return authenticator
26
+ raise AttributeError(f"module 'vnai.beam' has no attribute '{name}'")
vnai/beam/auth.py ADDED
@@ -0,0 +1,312 @@
1
+ """
2
+ Authentication and tier management.
3
+ Provides tier detection and API key management for rate limiting.
4
+ Includes centralized authentication state manager for deduplication.
5
+ """
6
+ import os
7
+ import json
8
+ import time
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Optional, Dict, Any
12
+ from datetime import datetime, timedelta
13
+ import threading
14
+ log = logging.getLogger(__name__)
15
+
16
+ class AuthStateManager:
17
+ def __init__(self, state_dir: Path):
18
+ self.state_dir = state_dir
19
+ self.state_file = state_dir /"auth_state.json"
20
+ self.state_dir.mkdir(parents=True, exist_ok=True)
21
+ self._state = self._load_state()
22
+
23
+ def _load_state(self) -> Dict:
24
+ if self.state_file.exists():
25
+ try:
26
+ with open(self.state_file,'r') as f:
27
+ return json.load(f)
28
+ except (json.JSONDecodeError, IOError) as e:
29
+ log.debug(f"Could not load auth state: {e}")
30
+ return self._default_state()
31
+ return self._default_state()
32
+
33
+ def _default_state(self) -> Dict:
34
+ return {
35
+ "session_id": self._generate_session_id(),
36
+ "authenticated": False,
37
+ "user": None,
38
+ "tier": None,
39
+ "auth_time": None,
40
+ "packages_notified": [],
41
+ "cache_ttl_minutes": 60
42
+ }
43
+
44
+ def _generate_session_id(self) -> str:
45
+ return datetime.now().isoformat()
46
+
47
+ def _save_state(self) -> None:
48
+ try:
49
+ with open(self.state_file,'w') as f:
50
+ json.dump(self._state, f, indent=2)
51
+ except IOError as e:
52
+ log.warning(f"Could not save auth state: {e}")
53
+
54
+ def _is_cache_valid(self) -> bool:
55
+ if not self._state.get("auth_time"):
56
+ return False
57
+ auth_time = datetime.fromisoformat(self._state["auth_time"])
58
+ ttl_minutes = self._state.get("cache_ttl_minutes", 60)
59
+ expiry = auth_time + timedelta(minutes=ttl_minutes)
60
+ return datetime.now() < expiry
61
+
62
+ def should_show_message(self, package_name: str) -> bool:
63
+ if not self._state.get("authenticated") or not self._is_cache_valid():
64
+ return True
65
+ packages_notified = self._state.get("packages_notified", [])
66
+ return len(packages_notified) == 0
67
+
68
+ def mark_authenticated(self, license_info: Dict, package_name: str) -> None:
69
+ self._state["authenticated"] = True
70
+ self._state["user"] = license_info.get("user","Unknown")
71
+ self._state["tier"] = license_info.get("tier","free")
72
+ self._state["auth_time"] = datetime.now().isoformat()
73
+ packages_notified = self._state.get("packages_notified", [])
74
+ if package_name not in packages_notified:
75
+ packages_notified.append(package_name)
76
+ self._state["packages_notified"] = packages_notified
77
+ self._save_state()
78
+
79
+ def get_cached_info(self) -> Optional[Dict]:
80
+ if not self._is_cache_valid():
81
+ return None
82
+ return {
83
+ "user": self._state.get("user"),
84
+ "tier": self._state.get("tier"),
85
+ "from_cache": True
86
+ }
87
+
88
+ def reset(self) -> None:
89
+ self._state = self._default_state()
90
+ self._save_state()
91
+
92
+ def get_auth_state_manager(project_dir: Path) -> AuthStateManager:
93
+ return AuthStateManager(project_dir)
94
+
95
+ class Authenticator:
96
+ TIER_LIMITS = {
97
+ "guest": {"min": 20,"hour": 1200,"day": 5000},
98
+ "free": {"min": 60,"hour": 3600,"day": 10000},
99
+ "bronze": {"min": 180,"hour": 10800,"day": 50000},
100
+ "silver": {"min": 300,"hour": 18000,"day": 100000},
101
+ "golden": {"min": 600,"hour": 36000,"day": 150000}
102
+ }
103
+
104
+ def __init__(self):
105
+ self.vnstock_dir = Path.home() /".vnstock"
106
+ self.api_key_file = self.vnstock_dir /"api_key.json"
107
+ self._cached_tier = None
108
+ self._cache_timestamp = 0
109
+ self._cache_ttl = 300
110
+
111
+ def get_tier(self, force_refresh: bool = False) -> str:
112
+ current_time = time.time()
113
+ if not force_refresh and self._cached_tier and (current_time - self._cache_timestamp) < self._cache_ttl:
114
+ return self._cached_tier
115
+ tier = self._detect_tier()
116
+ self._cached_tier = tier
117
+ self._cache_timestamp = current_time
118
+ log.debug(f"Detected tier: {tier}")
119
+ return tier
120
+
121
+ def _detect_tier(self) -> str:
122
+ tier_from_vnii = self._check_vnii_tier()
123
+ if tier_from_vnii:
124
+ return tier_from_vnii
125
+ if self._has_api_key():
126
+ return"free"
127
+ return"guest"
128
+
129
+ def _check_vnii_tier(self) -> Optional[str]:
130
+ try:
131
+ import vnii
132
+ from vnii.auth import authenticate
133
+ license_info = authenticate(self.vnstock_dir)
134
+ tier_string = license_info.get('tier','free')
135
+ log.debug(f"Got tier from vnii: {tier_string}")
136
+ return tier_string
137
+ except ImportError:
138
+ log.debug("vnii not installed")
139
+ return None
140
+ except SystemExit:
141
+ log.debug("vnii authentication failed")
142
+ return None
143
+ except Exception as e:
144
+ log.debug(f"Error checking vnii tier: {e}")
145
+ return None
146
+
147
+ def _has_api_key(self) -> bool:
148
+ if os.getenv('VNSTOCK_API_KEY'):
149
+ return True
150
+ if self.api_key_file.exists():
151
+ try:
152
+ with open(self.api_key_file,'r') as f:
153
+ data = json.load(f)
154
+ api_key = data.get('api_key','').strip()
155
+ return bool(api_key)
156
+ except Exception as e:
157
+ log.debug(f"Failed to read API key: {e}")
158
+ return False
159
+
160
+ def get_limits(self, tier: Optional[str] = None) -> Dict[str, int]:
161
+ if tier is None:
162
+ tier = self.get_tier()
163
+ return self.TIER_LIMITS.get(tier, self.TIER_LIMITS["guest"])
164
+
165
+ def get_tier_info(self) -> Dict:
166
+ tier = self.get_tier()
167
+ limits = self.get_limits(tier)
168
+ descriptions = {
169
+ "guest":"Khách (Guest - chưa đăng ký)",
170
+ "free":"Phiên bản cộng đồng (Community - có API key)",
171
+ "bronze":"Thành viên Bronze (Bronze Member)",
172
+ "silver":"Thành viên Silver (Silver Member)",
173
+ "golden":"Thành viên Golden (Golden Member)"
174
+ }
175
+ return {
176
+ "tier": tier,
177
+ "description": descriptions.get(tier,f"Gói {tier.title()}"),
178
+ "limits": {
179
+ "per_minute": limits["min"],
180
+ "per_hour": limits["hour"]
181
+ }
182
+ }
183
+
184
+ def setup_api_key(self, api_key: str) -> bool:
185
+ try:
186
+ self.vnstock_dir.mkdir(exist_ok=True)
187
+ api_key_data = {"api_key": api_key.strip()}
188
+ with open(self.api_key_file,'w') as f:
189
+ json.dump(api_key_data, f, indent=2)
190
+ print("✓ API key đã được lưu thành công! (API key saved successfully!)")
191
+ print("Bạn đang sử dụng Phiên bản cộng đồng (60 requests/phút)")
192
+ print("(You are using Community version - 60 requests/minute)")
193
+ print("\nĐể tham gia gói thành viên tài trợ (To join sponsor membership):")
194
+ print(" Truy cập: https://vnstocks.com/insiders-program")
195
+ log.info("API key setup completed")
196
+ return True
197
+ except Exception as e:
198
+ print(f"✗ Không thể lưu API key (Failed to save API key): {e}")
199
+ log.error(f"API key setup failed: {e}")
200
+ return False
201
+
202
+ def get_api_key(self) -> Optional[str]:
203
+ if os.getenv('VNSTOCK_API_KEY'):
204
+ return os.getenv('VNSTOCK_API_KEY')
205
+ if self.api_key_file.exists():
206
+ try:
207
+ with open(self.api_key_file,'r') as f:
208
+ data = json.load(f)
209
+ return data.get('api_key')
210
+ except Exception as e:
211
+ log.debug(f"Failed to read API key: {e}")
212
+ return None
213
+
214
+ def remove_api_key(self) -> bool:
215
+ try:
216
+ if self.api_key_file.exists():
217
+ self.api_key_file.unlink()
218
+ print("✓ API key đã được xóa (API key removed)")
219
+ print("Bạn đang ở chế độ Khách (20 requests/phút) (You are in Guest mode - 20 requests/minute)")
220
+ else:
221
+ print("Không tìm thấy API key (No API key found)")
222
+ log.info("API key removed")
223
+ return True
224
+ except Exception as e:
225
+ print(f"✗ Không thể xóa API key (Failed to remove API key): {e}")
226
+ log.error(f"API key removal failed: {e}")
227
+ return False
228
+
229
+ def _register_device_to_api(self, api_key: str) -> None:
230
+ try:
231
+ import requests
232
+ import platform
233
+ from vnai.scope.profile import inspector
234
+ from vnai.scope.device import IDEDetector
235
+ system_info = inspector.examine()
236
+ try:
237
+ ide_name, ide_info = IDEDetector.detect_ide()
238
+ except Exception:
239
+ ide_name ='Unknown'
240
+ ide_info = {}
241
+ payload = {
242
+ 'api_key': api_key,
243
+ 'device_id': system_info['machine_id'],
244
+ 'device_name': system_info.get('platform', platform.node()),
245
+ 'os_type': system_info['os_name'].lower(),
246
+ 'os_version': system_info.get('platform', platform.release()),
247
+ 'machine_info': {
248
+ 'platform': system_info.get('platform', platform.platform()),
249
+ 'machine': platform.machine(),
250
+ 'processor': platform.processor(),
251
+ 'system': platform.system(),
252
+ 'release': platform.release(),
253
+ 'python_version': system_info.get('python_version'),
254
+ 'environment': system_info.get('environment','unknown'),
255
+ 'ide_name': ide_name,
256
+ 'ide_detection_method': ide_info.get('detection_method'),
257
+ 'ide_frontend': ide_info.get('frontend')
258
+ }
259
+ }
260
+ url ='https://vnstocks.com/api/vnstock/auth/device-register'
261
+ try:
262
+ requests.post(url, json=payload, timeout=5)
263
+ log.debug("Device registered successfully to vnstocks.com")
264
+ except Exception as req_e:
265
+ log.debug(f"Device registration request failed: {req_e}")
266
+ except Exception as e:
267
+ log.debug(f"Device registration error: {e}")
268
+
269
+ def check_api_key_status(self) -> dict:
270
+ api_key = self.get_api_key()
271
+ if api_key:
272
+ preview = api_key[:15] +"..." if len(api_key) > 15 else api_key
273
+ tier_info = self.get_tier_info()
274
+ print(f"✓ API key: {preview}")
275
+ print(f"✓ Tier (Gói): {tier_info['tier']}")
276
+ print(f"✓ Giới hạn (Limits): {tier_info['limits']}")
277
+ return {
278
+ 'has_api_key': True,
279
+ 'api_key_preview': preview,
280
+ 'tier': tier_info['tier'],
281
+ 'limits': tier_info['limits']
282
+ }
283
+ else:
284
+ return {
285
+ 'has_api_key': False,
286
+ 'api_key_preview': None,
287
+ 'tier': self.get_tier(),
288
+ 'limits': self.get_limits()
289
+ }
290
+
291
+ def print_help(self):
292
+ print("\n" +"="*60)
293
+ print("VNSTOCK API KEY SETUP (Cài đặt API Key)")
294
+ print("="*60)
295
+ print("\n📋 Các gói sử dụng (Available Tiers):")
296
+ print(" • Khách (Guest): 20 requests/phút. Tối đa 4 kỳ báo cáo tài chính.")
297
+ print(" • Phiên bản cộng đồng (Community): 60 requests/phút. Tối đa 8 kỳ báo cáo tài chính.")
298
+ print(" • Thành viên tài trợ (Sponsor): 180-500 requests/phút.")
299
+ print("\n🔑 Lấy API Key (Get Your API Key):")
300
+ print(" 1. Truy cập: https://vnstocks.com/account")
301
+ print(" 2. Đăng ký hoặc đăng nhập (Sign up or log in)")
302
+ print(" 3. Sao chép API key của bạn (Copy your API key)")
303
+ print("\n💾 Cài đặt API Key (Setup API Key):")
304
+ print(" import vnai")
305
+ print(' vnai.setup_api_key("your_api_key_here")')
306
+ print("\n📊 Kiểm tra trạng thái (Check Status):")
307
+ print(" import vnai")
308
+ print(" vnai.check_api_key_status()")
309
+ print("\n🤝 Tham gia gói thành viên tài trợ (Join Vnstock Sponsor):")
310
+ print(" Truy cập: https://vnstocks.com/insiders-program")
311
+ print("="*60 +"\n")
312
+ authenticator = Authenticator()