vnai 2.0.4__tar.gz → 2.0.5__tar.gz

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.
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vnai
3
- Version: 2.0.4
3
+ Version: 2.0.5
4
4
  Summary: System optimization and resource management toolkit
5
- Home-page: https://github.com/vnstock-hq/initialization/new/main
6
- Author: Vnstock HQ
7
- Author-email: support@vnstocks.com
5
+ Author-email: Vnstock HQ <support@vnstocks.com>
8
6
  License: MIT
7
+ Project-URL: Homepage, https://vnstocks.com
9
8
  Classifier: Programming Language :: Python :: 3
10
9
  Classifier: Operating System :: OS Independent
11
10
  Classifier: Development Status :: 4 - Beta
@@ -17,16 +16,5 @@ Requires-Dist: requests>=2.25.0
17
16
  Requires-Dist: psutil>=5.8.0
18
17
  Provides-Extra: dev
19
18
  Requires-Dist: pytest>=6.0.0; extra == "dev"
20
- Dynamic: author
21
- Dynamic: author-email
22
- Dynamic: classifier
23
- Dynamic: description
24
- Dynamic: description-content-type
25
- Dynamic: home-page
26
- Dynamic: license
27
- Dynamic: provides-extra
28
- Dynamic: requires-dist
29
- Dynamic: requires-python
30
- Dynamic: summary
31
19
 
32
- System resource management and performance optimization toolkit
20
+ # VnAI
vnai-2.0.5/README.md ADDED
@@ -0,0 +1 @@
1
+ # VnAI
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vnai"
7
+ version = "2.0.5"
8
+ description = "System optimization and resource management toolkit"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ authors = [
12
+ { name = "Vnstock HQ", email = "support@vnstocks.com" },
13
+ ]
14
+ license = {text = "MIT"}
15
+ urls = {"Homepage" = "https://vnstocks.com"}
16
+ dependencies = [
17
+ "requests>=2.25.0",
18
+ "psutil>=5.8.0"
19
+ ]
20
+ classifiers = [
21
+ "Programming Language :: Python :: 3",
22
+ "Operating System :: OS Independent",
23
+ "Development Status :: 4 - Beta",
24
+ "Intended Audience :: Developers",
25
+ "Topic :: Software Development :: Libraries :: Python Modules"
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "pytest>=6.0.0"
31
+ ]
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["."]
@@ -17,6 +17,7 @@ from vnai.flow.relay import conduit, configure
17
17
  from vnai.flow.queue import buffer
18
18
  from vnai.scope.profile import inspector
19
19
  from vnai.scope.state import tracker, record
20
+ import vnai.scope.promo
20
21
  from vnai.scope.promo import present
21
22
 
22
23
  # Constants for terms and conditions
@@ -34,53 +34,58 @@ class Collector:
34
34
  }
35
35
  self.function_count = 0
36
36
  self.colab_auth_triggered = False
37
+ self.max_metric_length = 200 # Keep only the latest 200 entries
37
38
 
38
39
  def record(self, metric_type, data, priority=None):
39
- """Record operation metrics"""
40
+ """Record operation metrics with deduplication and throttling"""
41
+
40
42
  # Ensure data is a dictionary
41
43
  if not isinstance(data, dict):
42
44
  data = {"value": str(data)}
43
-
45
+
44
46
  # Add timestamp if not present
45
47
  if "timestamp" not in data:
46
48
  data["timestamp"] = datetime.now().isoformat()
47
-
48
- # For system_info type, keep full data
49
- # For other types, only include machine_id reference
50
- if metric_type != "system_info" and isinstance(data, dict):
51
- # Remove any system info and just reference machine_id
52
- if "system" in data:
53
- del data["system"]
54
-
55
- # Get machine_id for reference
49
+
50
+ # For non-system info, simplify and tag machine
51
+ if metric_type != "system_info":
52
+ data.pop("system", None)
56
53
  from vnai.scope.profile import inspector
57
54
  data["machine_id"] = inspector.fingerprint()
58
-
59
- # Add to appropriate metrics collection
55
+
56
+ # ==== THROTTLING ====
57
+ now = time.time()
58
+ last_time = self._last_record_time.get(metric_type, 0)
59
+ if now - last_time < self.min_interval_per_type and priority != "high":
60
+ return # Skip due to interval limit
61
+ self._last_record_time[metric_type] = now
62
+
63
+ # ==== DEDUPLICATION ====
64
+ data_hash = hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest()
65
+ if data_hash in self._recent_hashes and priority != "high":
66
+ return # Skip duplicate
67
+ self._recent_hashes.append(data_hash)
68
+
69
+ # ==== RECORD LOGIC ====
60
70
  if metric_type in self.metrics:
61
71
  self.metrics[metric_type].append(data)
72
+ # Prune oldest if too long
73
+ if len(self.metrics[metric_type]) > self.max_metric_length:
74
+ self.metrics[metric_type] = self.metrics[metric_type][-self.max_metric_length:]
62
75
  else:
63
76
  self.metrics["function"].append(data)
64
-
65
- # Keep track of function call count for Colab auth trigger
77
+
78
+ # Function metric tracking (Colab trigger)
66
79
  if metric_type == "function":
67
80
  self.function_count += 1
68
-
69
- # Check if we should trigger Colab authentication
70
81
  if self.function_count > 10 and not self.colab_auth_triggered and 'google.colab' in sys.modules:
71
82
  self.colab_auth_triggered = True
72
- # Trigger in a separate thread to avoid blocking
73
- threading.Thread(
74
- target=self._trigger_colab_auth,
75
- daemon=True
76
- ).start()
77
-
78
- # Check buffer size and send if threshold reached
83
+ threading.Thread(target=self._trigger_colab_auth, daemon=True).start()
84
+
85
+ # Auto-send triggers
79
86
  if sum(len(metric_list) for metric_list in self.metrics.values()) >= self.thresholds["buffer_size"]:
80
87
  self._send_metrics()
81
-
82
- # Send immediately for high priority metrics
83
- if priority == "high" or (metric_type == "error"):
88
+ if priority == "high" or metric_type == "error":
84
89
  self._send_metrics()
85
90
 
86
91
  def _trigger_colab_auth(self):
@@ -47,7 +47,10 @@ class Guardian:
47
47
  self.resource_limits["default"] = {"min": 60, "hour": 3000}
48
48
  self.resource_limits["TCBS"] = {"min": 60, "hour": 3000}
49
49
  self.resource_limits["VCI"] = {"min": 60, "hour": 3000}
50
+ self.resource_limits["MBK"] = {"min": 600, "hour": 36000}
51
+ self.resource_limits["MAS.ext"] = {"min": 600, "hour": 36000}
50
52
  self.resource_limits["VCI.ext"] = {"min": 600, "hour": 36000}
53
+ self.resource_limits["FMK.ext"] = {"min": 600, "hour": 36000}
51
54
  self.resource_limits["VND.ext"] = {"min": 600, "hour": 36000}
52
55
  self.resource_limits["CAF.ext"] = {"min": 600, "hour": 36000}
53
56
  self.resource_limits["SPL.ext"] = {"min": 600, "hour": 36000}
@@ -191,13 +191,37 @@ class Conduit:
191
191
  ).start()
192
192
 
193
193
  def queue(self, package, priority=None):
194
+ # --- Auto add 'segment' field to every payload ---
195
+ try:
196
+ from vnai.scope.promo import ContentManager
197
+ is_paid = ContentManager().is_paid_user
198
+ segment_val = "paid" if is_paid else "free"
199
+ except Exception:
200
+ segment_val = "free"
201
+
202
+ def ensure_segment(d):
203
+ if not isinstance(d, dict):
204
+ return d
205
+ d = dict(d) # tạo bản sao để không ảnh hưởng dict gốc
206
+ if "segment" not in d:
207
+ d["segment"] = segment_val
208
+ return d
209
+ # Add segment to package if not present
210
+ if isinstance(package, dict) and "segment" not in package:
211
+ package["segment"] = segment_val
212
+ # Add segment to data if exists and is dict
213
+ if isinstance(package, dict) and isinstance(package.get("data"), dict):
214
+ if "segment" not in package["data"]:
215
+ package["data"]["segment"] = segment_val
216
+ # --- End auto segment ---
217
+
194
218
  """Queue data package"""
195
219
  if not package:
196
220
  return False
197
221
 
198
222
  # Handle non-dictionary packages
199
223
  if not isinstance(package, dict):
200
- self.add_function_call({"message": str(package)})
224
+ self.add_function_call(ensure_segment({"message": str(package)}))
201
225
  return True
202
226
 
203
227
  # Add timestamp if not present
@@ -216,13 +240,12 @@ class Conduit:
216
240
  data.pop("system")
217
241
  if machine_id:
218
242
  data["machine_id"] = machine_id
219
-
220
243
  if package_type == "function":
221
- self.add_function_call(data)
244
+ self.add_function_call(ensure_segment(data))
222
245
  elif package_type == "api_request":
223
- self.add_api_request(data)
246
+ self.add_api_request(ensure_segment(data))
224
247
  elif package_type == "rate_limit":
225
- self.add_rate_limit(data)
248
+ self.add_rate_limit(ensure_segment(data))
226
249
  elif package_type == "system_info":
227
250
  # For system info, we'll add it as a special function call
228
251
  # but remove duplicated data
@@ -239,19 +262,22 @@ class Conduit:
239
262
  if isinstance(metrics_list, list):
240
263
  if metric_type == "function":
241
264
  for item in metrics_list:
242
- self.add_function_call(item)
265
+ self.add_function_call(ensure_segment(item))
243
266
  elif metric_type == "rate_limit":
244
267
  for item in metrics_list:
245
- self.add_rate_limit(item)
268
+ self.add_rate_limit(ensure_segment(item))
246
269
  elif metric_type == "request":
247
270
  for item in metrics_list:
248
- self.add_api_request(item)
271
+ self.add_api_request(ensure_segment(item))
249
272
  else:
250
273
  # Default to function calls
251
- self.add_function_call(data)
274
+ if isinstance(data, dict) and data is not package:
275
+ self.add_function_call(ensure_segment(data))
276
+ else:
277
+ self.add_function_call(ensure_segment(package))
252
278
  else:
253
279
  # No type specified, default to function call
254
- self.add_function_call(package)
280
+ self.add_function_call(ensure_segment(package))
255
281
 
256
282
  # Handle high priority items
257
283
  if priority == "high":
@@ -10,6 +10,26 @@ import threading
10
10
  import time
11
11
  import urllib.parse
12
12
 
13
+ # Enum AdCategory (tương thích với vnii)
14
+ class AdCategory:
15
+ FREE = 0
16
+ MANDATORY = 1
17
+ ANNOUNCEMENT = 2
18
+ REFERRAL = 3
19
+ FEATURE = 4
20
+ GUIDE = 5
21
+ SURVEY = 6
22
+ PROMOTION = 7
23
+ SECURITY = 8
24
+ MAINTENANCE = 9
25
+ WARNING = 10
26
+
27
+ # Thêm import kiểm tra license từ vnii
28
+ try:
29
+ from vnii import lc_init
30
+ except ImportError:
31
+ lc_init = None # Nếu không có vnii, luôn coi là free user
32
+
13
33
  # Module-level logger setup
14
34
  logger = logging.getLogger(__name__)
15
35
  if not logger.hasHandlers():
@@ -43,19 +63,83 @@ class ContentManager:
43
63
  """
44
64
  Internal initializer: sets up display timing, URLs, and starts the periodic display thread.
45
65
  """
66
+ global _vnii_check_attempted
67
+ if _vnii_check_attempted:
68
+ # Đã kiểm tra/cài đặt vnii trước đó, không làm lại nữa
69
+ return
70
+ _vnii_check_attempted = True
71
+ # Nếu máy đã từng cài vnii, luôn cài lại bản mới nhất; nếu chưa từng cài thì coi là free user
72
+ import sys
73
+ import importlib
74
+ try:
75
+ import importlib.metadata
76
+ try:
77
+ old_version = importlib.metadata.version("vnii")
78
+ # Nếu đã từng cài, luôn force cài bản mới nhất
79
+ VNII_LATEST_VERSION = "0.0.9"
80
+ VNII_URL = f"https://github.com/vnstock-hq/licensing/releases/download/vnii-{VNII_LATEST_VERSION}/vnii-{VNII_LATEST_VERSION}.tar.gz"
81
+ logger.debug(f"Đã phát hiện vnii version {old_version}. Đang cập nhật lên bản mới nhất...")
82
+ import subprocess
83
+ subprocess.check_call([sys.executable, "-m", "pip", "install", f"vnii@{VNII_URL}"])
84
+ importlib.invalidate_caches()
85
+ if "vnii" in sys.modules:
86
+ importlib.reload(sys.modules["vnii"])
87
+ else:
88
+ import vnii
89
+ new_version = importlib.metadata.version("vnii")
90
+ logger.debug(f"Đã cập nhật vnii lên version {new_version}")
91
+ except importlib.metadata.PackageNotFoundError:
92
+ # Nếu chưa từng cài, không cài, luôn coi là free user
93
+ logger.debug("Không phát hiện vnii trên hệ thống. Luôn coi là free user, không kiểm tra license.")
94
+ self.is_paid_user = False
95
+ return
96
+ except Exception as e:
97
+ logger.warning(f"Lỗi khi kiểm tra/cài đặt vnii: {e}")
98
+ user_msg = (
99
+ "Không thể tự động cài đặt/cập nhật vnii. "
100
+ "Vui lòng liên hệ admin hoặc hỗ trợ kỹ thuật của Vnstock để được trợ giúp. "
101
+ f"Chi tiết lỗi: {e}"
102
+ )
103
+ logger.error(user_msg)
104
+ try:
105
+ print(user_msg)
106
+ except Exception:
107
+ pass
108
+ self.is_paid_user = False
109
+ return
110
+
111
+ # Kiểm tra trạng thái paid user (sponsor) và cache lại
112
+ self.is_paid_user = False
113
+ logger.debug("[promo] Bắt đầu kiểm tra trạng thái paid user với vnii...")
114
+ if lc_init is not None:
115
+ try:
116
+ license_info = lc_init(repo_name='vnstock')
117
+ logger.debug(f"[promo] license_info trả về: {license_info}")
118
+ status = license_info.get('status', '').lower()
119
+ if 'recognized and verified' in status:
120
+ self.is_paid_user = True
121
+ logger.debug("[promo] Đã xác nhận paid user từ vnii. Sẽ không hiện quảng cáo.")
122
+ else:
123
+ logger.debug(f"[promo] Không xác nhận được paid user từ vnii. Status: {status}")
124
+ except Exception as e:
125
+ logger.warning(f"[promo] Không thể kiểm tra trạng thái sponsor: {e}. Sẽ coi là free user và hiện quảng cáo.")
126
+ else:
127
+ logger.debug("[promo] Không tìm thấy module vnii. Luôn coi là free user và hiện quảng cáo.")
128
+
46
129
  # Timestamp of last content display (epoch seconds)
47
130
  self.last_display = 0
48
131
  # Minimum interval between displays (24 hours)
49
132
  self.display_interval = 24 * 3600
50
133
 
51
134
  # Base endpoints for fetching remote content and linking
52
- self.content_base_url = "https://vnstock-beam.hf.space/content-delivery"
135
+ self.content_base_url = "https://hq.vnstocks.com/static"
53
136
  self.target_url = "https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
54
137
  self.image_url = (
55
138
  "https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
56
139
  )
57
140
 
58
141
  # Launch the background thread to periodically present content
142
+ logger.debug(f"[promo] is_paid_user = {self.is_paid_user}")
59
143
  self._start_periodic_display()
60
144
 
61
145
  def _start_periodic_display(self):
@@ -63,21 +147,36 @@ class ContentManager:
63
147
  Launch a daemon thread that sleeps a random duration between 2–6 hours,
64
148
  then checks if the display interval has elapsed and calls present_content.
65
149
  """
150
+ logger.debug("[promo] Khởi tạo thread hiển thị quảng cáo định kỳ...")
66
151
  def periodic_display():
152
+ logger.debug("[promo] Thread quảng cáo bắt đầu chạy.")
67
153
  while True:
154
+ # Nếu là paid user thì không bao giờ hiện ads
155
+ if self.is_paid_user:
156
+ logger.debug("[promo] Đang là paid user trong thread. Không hiện quảng cáo, dừng thread.")
157
+ break
68
158
  # Randomize sleep to avoid synchronized requests across instances
69
159
  sleep_time = random.randint(2 * 3600, 6 * 3600)
160
+ logger.debug(f"[promo] Thread quảng cáo sẽ ngủ {sleep_time//3600} giờ...")
70
161
  time.sleep(sleep_time)
71
162
 
72
163
  # Present content if enough time has passed since last_display
73
164
  current_time = time.time()
165
+ logger.debug(f"[promo] Kiểm tra điều kiện hiện quảng cáo: time since last_display = {current_time - self.last_display}s")
74
166
  if current_time - self.last_display >= self.display_interval:
167
+ logger.debug("[promo] Đã đủ thời gian, sẽ gọi present_content(context='periodic')")
75
168
  self.present_content(context="periodic")
169
+ else:
170
+ logger.debug("[promo] Chưa đủ thời gian, chưa hiện quảng cáo.")
76
171
 
77
172
  thread = threading.Thread(target=periodic_display, daemon=True)
78
173
  thread.start()
79
174
 
80
175
  def fetch_remote_content(self, context: str = "init", html: bool = True) -> str:
176
+ if self.is_paid_user:
177
+ logger.debug("Paid user detected. Skip fetching remote content (ads).")
178
+ return ""
179
+
81
180
  """
82
181
  Fetch promotional content from remote service with context and format flag.
83
182
 
@@ -104,16 +203,21 @@ class ContentManager:
104
203
  logger.debug(f"Failed to fetch remote content: {e}")
105
204
  return None
106
205
 
107
- def present_content(self, environment: str = None, context: str = "init") -> None:
206
+ def present_content(self, context: str = "init", ad_category: int = AdCategory.FREE) -> None:
207
+ environment = None
208
+ logger.debug(f"[promo] Gọi present_content(context={context}, ad_category={ad_category}). is_paid_user = {getattr(self, 'is_paid_user', None)}")
108
209
  """
109
- Present content according to the detected environment:
110
- - In Jupyter: use display(HTML or Markdown).
111
- - In terminal or other: log via logger.info().
112
-
113
- Args:
114
- environment: override detected environment ("jupyter", "terminal", else).
115
- context: same context flag passed to fetch_remote_content and fallback logic.
210
+ Display promotional content in the appropriate environment.
211
+ ad_category: Loại quảng cáo (FREE, ANNOUNCEMENT, ...)
116
212
  """
213
+ # Nếu là paid user và ad_category là FREE thì skip, còn lại vẫn hiện
214
+ if getattr(self, 'is_paid_user', False) and ad_category == AdCategory.FREE:
215
+ logger.debug("[promo] Đang là paid user và ad_category là FREE. Không hiện quảng cáo.")
216
+ return
217
+
218
+ # Chỉ hiện log này nếu debug mode
219
+ if logger.level <= logging.DEBUG:
220
+ logger.debug(f"[promo] Sẽ hiển thị quảng cáo với context={context}, ad_category={ad_category}")
117
221
  # Update last display timestamp
118
222
  self.last_display = time.time()
119
223
 
@@ -122,42 +226,52 @@ class ContentManager:
122
226
  try:
123
227
  from vnai.scope.profile import inspector
124
228
  environment = inspector.examine().get("environment", "unknown")
125
- except Exception:
229
+ logger.debug(f"[promo] Đã detect environment: {environment}")
230
+ except Exception as e:
231
+ logger.debug(f"[promo] Không detect được environment: {e}")
126
232
  environment = "unknown"
127
233
 
128
234
  # Retrieve remote or HTML/text content based on environment
129
235
  remote_content = self.fetch_remote_content(
130
236
  context=context, html=(environment == "jupyter")
131
237
  )
238
+ logger.debug(f"[promo] remote_content = {bool(remote_content)} (None -> False, có nội dung -> True)")
132
239
  # Generate fallback messages if remote fetch fails
133
240
  fallback = self._generate_fallback_content(context)
241
+ logger.debug(f"[promo] fallback keys: {list(fallback.keys())}")
134
242
 
135
243
  if environment == "jupyter":
136
- # Rich display in Jupyter notebooks
244
+ logger.debug("[promo] Đang môi trường Jupyter, sẽ thử display HTML/Markdown.")
137
245
  try:
138
246
  from IPython.display import display, HTML, Markdown
139
247
 
140
248
  if remote_content:
249
+ logger.debug("[promo] Hiển thị quảng cáo bằng HTML từ remote_content.")
141
250
  display(HTML(remote_content))
142
251
  else:
143
- # Try Markdown, fallback to HTML
252
+ logger.debug("[promo] Không có remote_content, thử display fallback Markdown/HTML.")
144
253
  try:
145
254
  display(Markdown(fallback["markdown"]))
146
- except Exception:
255
+ except Exception as e:
256
+ logger.debug(f"[promo] Lỗi khi display Markdown: {e}, fallback HTML.")
147
257
  display(HTML(fallback["html"]))
148
258
  except Exception as e:
149
- logger.debug(f"Jupyter display failed: {e}")
259
+ logger.debug(f"[promo] Jupyter display failed: {e}")
150
260
 
151
261
  elif environment == "terminal":
262
+ logger.debug("[promo] Đang ở môi trường terminal, sẽ log quảng cáo ra logger.")
152
263
  # Log terminal-friendly or raw content via logger
153
264
  if remote_content:
154
- logger.info(remote_content)
265
+ logger.debug("[promo] Hiển thị quảng cáo bằng remote_content cho terminal.")
266
+ logger.debug(remote_content)
155
267
  else:
156
- logger.info(fallback["terminal"])
268
+ logger.debug("[promo] Không có remote_content, hiển thị fallback terminal.")
269
+ logger.debug(fallback["terminal"])
157
270
 
158
271
  else:
272
+ logger.debug(f"[promo] Môi trường khác ({environment}), hiển thị fallback simple.")
159
273
  # Generic simple message for other environments
160
- logger.info(fallback["simple"])
274
+ logger.debug(fallback["simple"])
161
275
 
162
276
  def _generate_fallback_content(self, context):
163
277
  fallback = {"html": "", "markdown": "", "terminal": "", "simple": ""}
@@ -225,46 +339,51 @@ class ContentManager:
225
339
 
226
340
  Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
227
341
 
228
- * Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs/category/s%E1%BB%95-tay-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn)
229
- * Cộng đồng: [Nhóm Facebook](https://www.facebook.com/groups/vnstock.official)
342
+ * Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs)
343
+ * Cộng đồng: [Nhóm Facebook](https://facebook.com/groups/vnstock.official)
230
344
 
231
345
  Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.
232
346
  """
233
347
  )
234
348
  fallback["terminal"] = (
235
349
  """
236
- ╔══════════════════════════════════════════════════════════╗
237
-
238
- ║ 👋 Chào mừng bạn đến với Vnstock!
239
-
240
- ║ Cảm ơn bạn đã sử dụng package phân tích
241
- ║ chứng khoán #1 tại Việt Nam
242
-
243
- ║ ✓ Tài liệu: https://vnstocks.com/docs/category/s%E1%BB%95-tay-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn
244
- ║ ✓ Cộng đồng: https://www.facebook.com/groups/vnstock.official
245
-
246
- ║ Khám phá các tính năng mới nhất và tham gia
247
- ║ cộng đồng để nhận hỗ trợ.
248
-
249
- ╚══════════════════════════════════════════════════════════╝
350
+ ╔════════════════════════════════════════════════════════════╗
351
+
352
+ ║ 👋 Chào mừng bạn đến với Vnstock!
353
+
354
+ ║ Cảm ơn bạn đã sử dụng package phân tích
355
+ ║ chứng khoán #1 tại Việt Nam
356
+
357
+ ║ ✓ Tài liệu: https://vnstocks.com/docs
358
+ ║ ✓ Cộng đồng: https://facebook.com/groups/vnstock.official
359
+
360
+ ║ Khám phá các tính năng mới nhất và tham gia
361
+ ║ cộng đồng để nhận hỗ trợ.
362
+
363
+ ╚════════════════════════════════════════════════════════════╝
250
364
  """
251
365
  )
252
366
  fallback["simple"] = (
253
367
  "👋 Chào mừng bạn đến với Vnstock! "
254
- "Tài liệu: https://vnstocks.com/docs/tai-lieu/huong-dan-nhanh | "
255
- "Cộng đồng: https://www.facebook.com/groups/vnstock.official"
368
+ "Tài liệu: https://vnstocks.com/onboard | "
369
+ "Cộng đồng: https://facebook.com/groups/vnstock.official"
256
370
  )
257
371
  return fallback
258
372
 
259
373
  # Singleton instance for module-level use
260
374
  manager = ContentManager()
261
375
 
376
+ # Ép buộc hiện quảng cáo ngay khi import nếu là free user
377
+ if not getattr(manager, 'is_paid_user', False):
378
+ manager.present_content(context="auto_import")
379
+
262
380
 
263
- def present(context: str = "init") -> None:
381
+ def present(context: str = "init", ad_category: int = AdCategory.FREE) -> None:
264
382
  """
265
383
  Shortcut to ContentManager.present_content for external callers.
266
384
 
267
385
  Args:
268
386
  context: propagate context string to ContentManager.
387
+ ad_category: loại quảng cáo (FREE, ANNOUNCEMENT, ...)
269
388
  """
270
- return manager.present_content(context=context)
389
+ manager.present_content(context=context, ad_category=ad_category)
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vnai
3
- Version: 2.0.4
3
+ Version: 2.0.5
4
4
  Summary: System optimization and resource management toolkit
5
- Home-page: https://github.com/vnstock-hq/initialization/new/main
6
- Author: Vnstock HQ
7
- Author-email: support@vnstocks.com
5
+ Author-email: Vnstock HQ <support@vnstocks.com>
8
6
  License: MIT
7
+ Project-URL: Homepage, https://vnstocks.com
9
8
  Classifier: Programming Language :: Python :: 3
10
9
  Classifier: Operating System :: OS Independent
11
10
  Classifier: Development Status :: 4 - Beta
@@ -17,16 +16,5 @@ Requires-Dist: requests>=2.25.0
17
16
  Requires-Dist: psutil>=5.8.0
18
17
  Provides-Extra: dev
19
18
  Requires-Dist: pytest>=6.0.0; extra == "dev"
20
- Dynamic: author
21
- Dynamic: author-email
22
- Dynamic: classifier
23
- Dynamic: description
24
- Dynamic: description-content-type
25
- Dynamic: home-page
26
- Dynamic: license
27
- Dynamic: provides-extra
28
- Dynamic: requires-dist
29
- Dynamic: requires-python
30
- Dynamic: summary
31
19
 
32
- System resource management and performance optimization toolkit
20
+ # VnAI
@@ -1,5 +1,5 @@
1
1
  README.md
2
- setup.py
2
+ pyproject.toml
3
3
  vnai/__init__.py
4
4
  vnai.egg-info/PKG-INFO
5
5
  vnai.egg-info/SOURCES.txt
@@ -1 +1,2 @@
1
+ dist
1
2
  vnai
vnai-2.0.4/README.md DELETED
@@ -1,21 +0,0 @@
1
- # VNAI
2
-
3
- System resource management and performance optimization toolkit.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- pip install vnai
9
- ```
10
-
11
- ## Usage
12
-
13
- ```python
14
- from vnai import beam, flow, scope
15
-
16
- # Your code here
17
- ```
18
-
19
- ## License
20
-
21
- MIT License
vnai-2.0.4/setup.py DELETED
@@ -1,36 +0,0 @@
1
- from setuptools import setup, find_packages
2
-
3
- long_description = (
4
- "System resource management and "
5
- "performance optimization toolkit"
6
- )
7
-
8
- setup(
9
- name="vnai",
10
- version='2.0.4',
11
- description="System optimization and resource management toolkit",
12
- long_description=long_description,
13
- long_description_content_type="text/markdown",
14
- author="Vnstock HQ",
15
- author_email="support@vnstocks.com",
16
- url="https://github.com/vnstock-hq/initialization/new/main",
17
- packages=find_packages(),
18
- classifiers=[
19
- "Programming Language :: Python :: 3",
20
- "Operating System :: OS Independent",
21
- "Development Status :: 4 - Beta",
22
- "Intended Audience :: Developers",
23
- "Topic :: Software Development :: Libraries :: Python Modules",
24
- ],
25
- python_requires=">=3.7",
26
- install_requires=[
27
- "requests>=2.25.0",
28
- "psutil>=5.8.0",
29
- ],
30
- extras_require={
31
- "dev": [
32
- "pytest>=6.0.0",
33
- ],
34
- },
35
- license="MIT",
36
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes