vnai 2.1.7__py3-none-any.whl → 2.1.8__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/scope/promo.py CHANGED
@@ -1,278 +1,375 @@
1
- import logging
2
- import requests
3
- from datetime import datetime
4
- import random
5
- import threading
6
- import time
7
- import urllib.parse
8
- _vnii_check_attempted = False
9
-
10
- class AdCategory:
11
- FREE = 0
12
- MANDATORY = 1
13
- ANNOUNCEMENT = 2
14
- REFERRAL = 3
15
- FEATURE = 4
16
- GUIDE = 5
17
- SURVEY = 6
18
- PROMOTION = 7
19
- SECURITY = 8
20
- MAINTENANCE = 9
21
- WARNING = 10
22
- try:
23
- from vnii import lc_init
24
- except ImportError:
25
- lc_init = None
26
- logger = logging.getLogger(__name__)
27
- if not logger.hasHandlers():
28
- handler = logging.StreamHandler()
29
- handler.setFormatter(logging.Formatter('%(message)s'))
30
- logger.addHandler(handler)
31
- logger.setLevel(logging.ERROR)
32
-
33
- class ContentManager:
34
- _instance = None
35
- _lock = threading.Lock()
36
-
37
- def __new__(cls):
38
- with cls._lock:
39
- if cls._instance is None:
40
- cls._instance = super(ContentManager, cls).__new__(cls)
41
- cls._instance._initialize()
42
- return cls._instance
43
-
44
- def _initialize(self):
45
- self.content_base_url ="https://hq.vnstocks.com/content-delivery"
46
- global _vnii_check_attempted
47
- if _vnii_check_attempted:
48
- return
49
- _vnii_check_attempted = True
50
- import sys
51
- import importlib
52
- try:
53
- import importlib.metadata
54
- VNII_LATEST_VERSION ="0.0.9"
55
- VNII_URL =f"https://github.com/vnstock-hq/licensing/releases/download/vnii-{VNII_LATEST_VERSION}/vnii-{VNII_LATEST_VERSION}.tar.gz"
56
- import subprocess
57
- try:
58
- old_version = importlib.metadata.version("vnii")
59
- if old_version != VNII_LATEST_VERSION:
60
- try:
61
- subprocess.check_call([
62
- sys.executable,"-m","pip","install",f"vnii@{VNII_URL}","--quiet"
63
- ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
64
- importlib.invalidate_caches()
65
- if"vnii" in sys.modules:
66
- importlib.reload(sys.modules["vnii"])
67
- else:
68
- import vnii
69
- new_version = importlib.metadata.version("vnii")
70
- except Exception as e:
71
- logger.error(f"Lỗi khi cài đặt vnii: {e}")
72
- pass
73
- except importlib.metadata.PackageNotFoundError:
74
- self.is_paid_user = False
75
- return
76
- except Exception as e:
77
- logger.error(f"Lỗi khi kiểm tra/cài đặt vnii: {e}")
78
- user_msg = (
79
- "Không thể tự động cài đặt/cập nhật vnii. "
80
- "Vui lòng liên hệ admin hoặc hỗ trợ kỹ thuật của Vnstock để được trợ giúp. "
81
- f"Chi tiết lỗi: {e}"
82
- )
83
- logger.error(user_msg)
84
- try:
85
- print(user_msg)
86
- except Exception:
87
- pass
88
- self.is_paid_user = False
89
- return
90
- self.is_paid_user = False
91
- if lc_init is not None:
92
- try:
93
- license_info = lc_init(repo_name='vnstock')
94
- status = license_info.get('status','').lower()
95
- if'recognized and verified' in status:
96
- self.is_paid_user = True
97
- except Exception as e:
98
- pass
99
- else:
100
- pass
101
- self.last_display = 0
102
- self.display_interval = 24 * 3600
103
- self.content_base_url ="https://hq.vnstocks.com/content-delivery"
104
- self.target_url ="https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
105
- self.image_url = (
106
- "https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
107
- )
108
- self._start_periodic_display()
109
-
110
- def _start_periodic_display(self):
111
- def periodic_display():
112
- while True:
113
- if self.is_paid_user:
114
- break
115
- sleep_time = random.randint(2 * 3600, 6 * 3600)
116
- time.sleep(sleep_time)
117
- current_time = time.time()
118
- if current_time - self.last_display >= self.display_interval:
119
- self.present_content(context="periodic")
120
- else:
121
- pass
122
- thread = threading.Thread(target=periodic_display, daemon=True)
123
- thread.start()
124
-
125
- def fetch_remote_content(self, context: str ="init", html: bool = True) -> str:
126
- if self.is_paid_user:
127
- return""
128
- """
129
- Fetch promotional content from remote service with context and format flag.
130
- Args:
131
- context: usage context (e.g., "init", "periodic", "loop").
132
- html: if True, request HTML; otherwise plain text.
133
- Returns:
134
- The content string on HTTP 200, or None on failure.
135
- """
136
- try:
137
- params = {"context": context,"html":"true" if html else"false"}
138
- url =f"{self.content_base_url}?{urllib.parse.urlencode(params)}"
139
- response = requests.get(url, timeout=3)
140
- if response.status_code == 200:
141
- return response.text
142
- logger.error(f"Non-200 response fetching content: {response.status_code}")
143
- return None
144
- except Exception as e:
145
- logger.error(f"Failed to fetch remote content: {e}")
146
- return None
147
-
148
- def present_content(self, context: str ="init", ad_category: int = AdCategory.FREE) -> None:
149
- environment = None
150
- if getattr(self,'is_paid_user', False) and ad_category == AdCategory.FREE:
151
- return
152
- self.last_display = time.time()
153
- if environment is None:
154
- try:
155
- from vnai.scope.profile import inspector
156
- environment = inspector.examine().get("environment","unknown")
157
- except Exception as e:
158
- logger.error(f"Không detect được environment: {e}")
159
- environment ="unknown"
160
- remote_content = self.fetch_remote_content(
161
- context=context, html=(environment =="jupyter")
162
- )
163
- fallback = self._generate_fallback_content(context)
164
- if environment =="jupyter":
165
- try:
166
- from IPython.display import display, HTML, Markdown
167
- if remote_content:
168
- display(HTML(remote_content))
169
- else:
170
- try:
171
- display(Markdown(fallback["markdown"]))
172
- except Exception as e:
173
- display(HTML(fallback["html"]))
174
- except Exception as e:
175
- pass
176
- elif environment =="terminal":
177
- if remote_content:
178
- print(remote_content)
179
- else:
180
- print(fallback["terminal"])
181
- else:
182
- print(fallback["simple"])
183
-
184
- def _generate_fallback_content(self, context):
185
- fallback = {"html":"","markdown":"","terminal":"","simple":""}
186
- if context =="loop":
187
- fallback["html"] = (
188
- f"""
189
- <div style="border: 1px solid #e74c3c; padding: 15px; border-radius: 5px; margin: 10px 0;">
190
- <h3 style="color: #e74c3c;">⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests</h3>
191
- <p>Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:</p>
192
- <ul>
193
- <li>Thêm thời gian chờ giữa các lần gọi API</li>
194
- <li>Sử dụng xử lý theo batch thay vì lặp liên tục</li>
195
- <li>Tham gia gói tài trợ <a href="https://vnstocks.com/insiders-program" style="color: #3498db;">Vnstock Insider</a> để tăng 5X giới hạn API</li>
196
- </ul>
197
- </div>
198
- """
199
- )
200
- fallback["markdown"] = (
201
- """
202
- ## ⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests
203
- Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:
204
- * Thêm thời gian chờ giữa các lần gọi API
205
- * Sử dụng xử theo batch thay vì lặp liên tục
206
- * Tham gia gói tài trợ [Vnstock Insider](https://vnstocks.com/insiders-program) để tăng 5X giới hạn API
207
- """
208
- )
209
- fallback["terminal"] = (
210
- """
211
- ╔═════════════════════════════════════════════════════════════════╗
212
- ║ ║
213
- ║ 🚫 ĐANG BỊ CHẶN BỞI GIỚI HẠN API? GIẢI PHÁP ĐÂY! ║
214
- ║ ║
215
- ║ ✓ Tăng ngay 500% tốc độ gọi API - Không còn lỗi RateLimit ║
216
- ║ ✓ Tiết kiệm 85% thời gian chờ đợi giữa các request ║
217
- ║ ║
218
- ║ ➤ NÂNG CẤP NGAY VỚI GÓI TÀI TRỢ VNSTOCK: ║
219
- ║ https://vnstocks.com/insiders-program ║
220
- ║ ║
221
- ╚═════════════════════════════════════════════════════════════════╝
222
- """
223
- )
224
- fallback["simple"] = (
225
- "🚫 Đang bị giới hạn API? Tăng tốc độ gọi API lên 500% với gói "
226
- "Vnstock Insider: https://vnstocks.com/insiders-program"
227
- )
228
- else:
229
- fallback["html"] = (
230
- f"""
231
- <div style="border: 1px solid #3498db; padding: 15px; border-radius: 5px; margin: 10px 0;">
232
- <h3 style="color: #3498db;">👋 Chào mừng bạn đến với Vnstock!</h3>
233
- <p>Cảm ơn bạn đã sử dụng thư viện phân tích chứng khoán #1 tại Việt Nam cho Python</p>
234
- <ul>
235
- <li>Tài liệu: <a href="https://vnstocks.com/docs/category/s%E1%BB%95-tay-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn" style="color: #3498db;">vnstocks.com/docs</a></li>
236
- <li>Cộng đồng: <a href="https://www.facebook.com/groups/vnstock.official" style="color: #3498db;">vnstocks.com/community</a></li>
237
- </ul>
238
- <p>Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.</p>
239
- </div>
240
- """
241
- )
242
- fallback["markdown"] = (
243
- """
244
- ## 👋 Chào mừng bạn đến với Vnstock!
245
- Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
246
- * Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs)
247
- * Cộng đồng: [Nhóm Facebook](https://facebook.com/groups/vnstock.official)
248
- Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.
249
- """
250
- )
251
- fallback["terminal"] = (
252
- """
253
- ╔════════════════════════════════════════════════════════════╗
254
- ║ ║
255
- ║ 👋 Chào mừng bạn đến với Vnstock! ║
256
- ║ ║
257
- ║ Cảm ơn bạn đã sử dụng package phân tích ║
258
- ║ chứng khoán #1 tại Việt Nam ║
259
- ║ ║
260
- ║ ✓ Tài liệu: https://vnstocks.com/docs ║
261
- ║ ✓ Cộng đồng: https://facebook.com/groups/vnstock.official ║
262
- ║ ║
263
- ║ Khám phá các tính năng mới nhất và tham gia ║
264
- ║ cộng đồng để nhận hỗ trợ. ║
265
- ║ ║
266
- ╚════════════════════════════════════════════════════════════╝
267
- """
268
- )
269
- fallback["simple"] = (
270
- "👋 Chào mừng bạn đến với Vnstock! "
271
- "Tài liệu: https://vnstocks.com/onboard | "
272
- "Cộng đồng: https://facebook.com/groups/vnstock.official"
273
- )
274
- return fallback
275
- manager = ContentManager()
276
-
277
- def present(context: str ="init", ad_category: int = AdCategory.FREE) -> None:
278
- manager.present_content(context=context, ad_category=ad_category)
1
+ import logging
2
+ import requests
3
+ from datetime import datetime
4
+ import random
5
+ import threading
6
+ import time
7
+ import urllib.parse
8
+
9
+ _vnii_check_attempted = False
10
+
11
+ class AdCategory:
12
+ FREE = 0
13
+ MANDATORY = 1
14
+ ANNOUNCEMENT = 2
15
+ REFERRAL = 3
16
+ FEATURE = 4
17
+ GUIDE = 5
18
+ SURVEY = 6
19
+ PROMOTION = 7
20
+ SECURITY = 8
21
+ MAINTENANCE = 9
22
+ WARNING = 10
23
+
24
+ # Thêm import kiểm tra license từ vnii
25
+ try:
26
+ from vnii import lc_init
27
+ except ImportError:
28
+ lc_init = None # Nếu không có vnii, không xác định được trạng thái license
29
+
30
+ # Module-level logger setup
31
+ logger = logging.getLogger(__name__)
32
+ if not logger.hasHandlers():
33
+ # Add a simple stream handler that only outputs the message text
34
+ handler = logging.StreamHandler()
35
+ handler.setFormatter(logging.Formatter('%(message)s'))
36
+ logger.addHandler(handler)
37
+ # Set to ERROR level to minimize output
38
+ logger.setLevel(logging.ERROR)
39
+
40
+ class ContentManager:
41
+ """
42
+ Singleton manager to fetch remote or fallback promotional content and
43
+ present it in different environments (Jupyter, terminal, other).
44
+
45
+ Displays content automatically at randomized intervals via a background thread.
46
+ """
47
+ _instance = None
48
+ _lock = threading.Lock()
49
+
50
+ def __new__(cls):
51
+ """
52
+ Ensure only one instance of ContentManager is created (thread-safe).
53
+ """
54
+ with cls._lock:
55
+ if cls._instance is None:
56
+ cls._instance = super(ContentManager, cls).__new__(cls)
57
+ cls._instance._initialize()
58
+ return cls._instance
59
+
60
+ def _initialize(self, debug=False):
61
+ """
62
+ Internal initializer: sets up display timing, URLs, and starts the periodic display thread.
63
+ """
64
+ # Set up content base URL
65
+ self.content_base_url = "https://hq.vnstocks.com/content-delivery"
66
+ self.is_paid_user = None # Mặc định: chưa xác định
67
+ self.license_checked = False
68
+ self._debug = debug
69
+ global _vnii_check_attempted
70
+ if _vnii_check_attempted:
71
+ # Đã kiểm tra/cài đặt vnii trước đó, không làm lại nữa
72
+ return
73
+ _vnii_check_attempted = True
74
+ # 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ì không xác định được trạng thái license
75
+ import sys
76
+ import importlib
77
+ try:
78
+ import importlib.metadata
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
+ import subprocess
82
+ try:
83
+ old_version = importlib.metadata.version("vnii")
84
+ # Chỉ cài nếu version chưa đúng
85
+ if old_version != VNII_LATEST_VERSION:
86
+ try:
87
+ subprocess.check_call([
88
+ sys.executable, "-m", "pip", "install", f"vnii@{VNII_URL}", "--quiet"
89
+ ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
90
+ importlib.invalidate_caches()
91
+ if "vnii" in sys.modules:
92
+ importlib.reload(sys.modules["vnii"])
93
+ else:
94
+ import vnii
95
+ new_version = importlib.metadata.version("vnii")
96
+ except Exception as e:
97
+ logger.error(f"Lỗi khi cài đặt vnii: {e}")
98
+ pass
99
+ # Nếu đã đúng version thì không log gì cả
100
+ except importlib.metadata.PackageNotFoundError:
101
+ # Nếu chưa từng cài, không cài, luôn coi là free user
102
+ self.is_paid_user = False
103
+ return
104
+ except Exception as e:
105
+ logger.error(f"Lỗi khi kiểm tra/cài đặt vnii: {e}")
106
+ user_msg = (
107
+ "Không thể tự động cài đặt/cập nhật vnii. "
108
+ "Vui lòng liên hệ admin hoặc hỗ trợ kỹ thuật của Vnstock để được trợ giúp. "
109
+ f"Chi tiết lỗi: {e}"
110
+ )
111
+ logger.error(user_msg)
112
+ try:
113
+ print(user_msg)
114
+ except Exception:
115
+ pass
116
+ self.is_paid_user = False
117
+ return
118
+
119
+ # Kiểm tra trạng thái paid user (sponsor) và cache lại
120
+ if lc_init is not None:
121
+ try:
122
+ license_info = lc_init(repo_name='vnstock', debug=self._debug)
123
+ status = license_info.get('status', '').lower()
124
+ if self._debug:
125
+ logger.info(f"License check result: {status}")
126
+ if 'recognized and verified' in status:
127
+ self.is_paid_user = True
128
+ if self._debug:
129
+ logger.info("Detected paid user (license recognized and verified).")
130
+ else:
131
+ self.is_paid_user = False
132
+ if self._debug:
133
+ logger.info("Detected free user (license not recognized/verified).")
134
+ self.license_checked = True
135
+ except Exception as e:
136
+ if self._debug:
137
+ logger.error(f"Lỗi khi kiểm tra license với lc_init: {e}")
138
+ self.is_paid_user = None
139
+ else:
140
+ if self._debug:
141
+ logger.warning("Không tìm thấy package vnii. Không xác định được trạng thái paid user.")
142
+ self.is_paid_user = None
143
+
144
+ # Timestamp of last content display (epoch seconds)
145
+ self.last_display = 0
146
+ # Minimum interval between displays (24 hours)
147
+ self.display_interval = 24 * 3600
148
+
149
+ # Base endpoints for fetching remote content and linking
150
+ self.content_base_url = "https://hq.vnstocks.com/content-delivery"
151
+ self.target_url = "https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
152
+ self.image_url = (
153
+ "https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
154
+ )
155
+
156
+ # Launch the background thread to periodically present content
157
+ self._start_periodic_display()
158
+
159
+ def _start_periodic_display(self):
160
+ """
161
+ Launch a daemon thread that sleeps a random duration between 2–6 hours,
162
+ then checks if the display interval has elapsed and calls present_content.
163
+ """
164
+ def periodic_display():
165
+ while True:
166
+ # Nếu paid user thì không bao giờ hiện ads
167
+ if self.is_paid_user:
168
+ break
169
+ # Randomize sleep to avoid synchronized requests across instances
170
+ sleep_time = random.randint(2 * 3600, 6 * 3600)
171
+ time.sleep(sleep_time)
172
+
173
+ # Present content if enough time has passed since last_display
174
+ current_time = time.time()
175
+ if current_time - self.last_display >= self.display_interval:
176
+ self.present_content(context="periodic")
177
+ else:
178
+ pass
179
+
180
+ thread = threading.Thread(target=periodic_display, daemon=True)
181
+ thread.start()
182
+
183
+ def fetch_remote_content(self, context: str = "init", html: bool = True) -> str:
184
+ if self.is_paid_user:
185
+ return ""
186
+
187
+ """
188
+ Fetch promotional content from remote service with context and format flag.
189
+
190
+ Args:
191
+ context: usage context (e.g., "init", "periodic", "loop").
192
+ html: if True, request HTML; otherwise plain text.
193
+
194
+ Returns:
195
+ The content string on HTTP 200, or None on failure.
196
+ """
197
+ try:
198
+ # Build query params and URL
199
+ params = {"context": context, "html": "true" if html else "false"}
200
+ url = f"{self.content_base_url}?{urllib.parse.urlencode(params)}"
201
+
202
+ response = requests.get(url, timeout=3)
203
+ if response.status_code == 200:
204
+ return response.text
205
+ # Log non-200 responses at debug level
206
+ logger.error(f"Non-200 response fetching content: {response.status_code}")
207
+ return None
208
+ except Exception as e:
209
+ # Log exceptions without interrupting user code
210
+ logger.error(f"Failed to fetch remote content: {e}")
211
+ return None
212
+
213
+ def present_content(self, context: str = "init", ad_category: int = AdCategory.FREE) -> None:
214
+ environment = None
215
+ """
216
+ Display promotional content in the appropriate environment.
217
+ ad_category: Loại quảng cáo (FREE, ANNOUNCEMENT, ...)
218
+ """
219
+ # Nếu là paid user và ad_category là FREE thì skip, còn lại vẫn hiện
220
+ if getattr(self, 'is_paid_user', False) and ad_category == AdCategory.FREE:
221
+ return
222
+
223
+ # Update last display timestamp
224
+ self.last_display = time.time()
225
+
226
+ # Auto-detect environment if not provided
227
+ if environment is None:
228
+ try:
229
+ from vnai.scope.profile import inspector
230
+ environment = inspector.examine().get("environment", "unknown")
231
+ except Exception as e:
232
+ logger.error(f"Không detect được environment: {e}")
233
+ environment = "unknown"
234
+
235
+ # Retrieve remote or HTML/text content based on environment
236
+ remote_content = self.fetch_remote_content(
237
+ context=context, html=(environment == "jupyter")
238
+ )
239
+
240
+ # Generate fallback messages if remote fetch fails
241
+ fallback = self._generate_fallback_content(context)
242
+
243
+ if environment == "jupyter":
244
+ try:
245
+ from IPython.display import display, HTML, Markdown
246
+
247
+ if remote_content:
248
+ display(HTML(remote_content))
249
+ else:
250
+ try:
251
+ display(Markdown(fallback["markdown"]))
252
+ except Exception as e:
253
+ display(HTML(fallback["html"]))
254
+ except Exception as e:
255
+ pass
256
+
257
+ elif environment == "terminal":
258
+ # Hiển thị quảng cáo dạng text ra terminal
259
+ if remote_content:
260
+ print(remote_content)
261
+ else:
262
+ print(fallback["terminal"])
263
+
264
+ else:
265
+ print(fallback["simple"])
266
+
267
+ def _generate_fallback_content(self, context):
268
+ fallback = {"html": "", "markdown": "", "terminal": "", "simple": ""}
269
+
270
+ if context == "loop":
271
+ fallback["html"] = (
272
+ f"""
273
+ <div style="border: 1px solid #e74c3c; padding: 15px; border-radius: 5px; margin: 10px 0;">
274
+ <h3 style="color: #e74c3c;">⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests</h3>
275
+ <p>Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:</p>
276
+ <ul>
277
+ <li>Thêm thời gian chờ giữa các lần gọi API</li>
278
+ <li>Sử dụng xử lý theo batch thay vì lặp liên tục</li>
279
+ <li>Tham gia gói tài trợ <a href="https://vnstocks.com/insiders-program" style="color: #3498db;">Vnstock Insider</a> để tăng 5X giới hạn API</li>
280
+ </ul>
281
+ </div>
282
+ """
283
+ )
284
+ fallback["markdown"] = (
285
+ """
286
+ ## ⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests
287
+
288
+ Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:
289
+ * Thêm thời gian chờ giữa các lần gọi API
290
+ * Sử dụng xử lý theo batch thay vì lặp liên tục
291
+ * Tham gia gói tài trợ [Vnstock Insider](https://vnstocks.com/insiders-program) để tăng 5X giới hạn API
292
+ """
293
+ )
294
+ fallback["terminal"] = (
295
+ """
296
+ ╔═════════════════════════════════════════════════════════════════╗
297
+ ║ ║
298
+ ║ 🚫 ĐANG BỊ CHẶN BỞI GIỚI HẠN API? GIẢI PHÁP Ở ĐÂY! ║
299
+ ║ ║
300
+ ║ ✓ Tăng ngay 500% tốc độ gọi API - Không còn lỗi RateLimit ║
301
+ ║ ✓ Tiết kiệm 85% thời gian chờ đợi giữa các request ║
302
+ ║ ║
303
+ ║ ➤ NÂNG CẤP NGAY VỚI GÓI TÀI TRỢ VNSTOCK: ║
304
+ ║ https://vnstocks.com/insiders-program ║
305
+ ║ ║
306
+ ╚═════════════════════════════════════════════════════════════════╝
307
+ """
308
+ )
309
+ fallback["simple"] = (
310
+ "🚫 Đang bị giới hạn API? Tăng tốc độ gọi API lên 500% với gói "
311
+ "Vnstock Insider: https://vnstocks.com/insiders-program"
312
+ )
313
+ else:
314
+ fallback["html"] = (
315
+ f"""
316
+ <div style="border: 1px solid #3498db; padding: 15px; border-radius: 5px; margin: 10px 0;">
317
+ <h3 style="color: #3498db;">👋 Chào mừng bạn đến với Vnstock!</h3>
318
+ <p>Cảm ơn bạn đã sử dụng thư viện phân tích chứng khoán #1 tại Việt Nam cho Python</p>
319
+ <ul>
320
+ <li>Tài liệu: <a href="https://vnstocks.com/docs/category/s%E1%BB%95-tay-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn" style="color: #3498db;">vnstocks.com/docs</a></li>
321
+ <li>Cộng đồng: <a href="https://www.facebook.com/groups/vnstock.official" style="color: #3498db;">vnstocks.com/community</a></li>
322
+ </ul>
323
+ <p>Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.</p>
324
+ </div>
325
+ """
326
+ )
327
+ fallback["markdown"] = (
328
+ """
329
+ ## 👋 Chào mừng bạn đến với Vnstock!
330
+
331
+ Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
332
+
333
+ * Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs)
334
+ * Cộng đồng: [Nhóm Facebook](https://facebook.com/groups/vnstock.official)
335
+
336
+ Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.
337
+ """
338
+ )
339
+ fallback["terminal"] = (
340
+ """
341
+ ╔════════════════════════════════════════════════════════════╗
342
+ ║ ║
343
+ ║ 👋 Chào mừng bạn đến với Vnstock! ║
344
+ ║ ║
345
+ ║ Cảm ơn bạn đã sử dụng package phân tích ║
346
+ ║ chứng khoán #1 tại Việt Nam ║
347
+ ║ ║
348
+ ║ ✓ Tài liệu: https://vnstocks.com/docs ║
349
+ ║ ✓ Cộng đồng: https://facebook.com/groups/vnstock.official ║
350
+ ║ ║
351
+ ║ Khám phá các tính năng mới nhất và tham gia ║
352
+ ║ cộng đồng để nhận hỗ trợ. ║
353
+ ║ ║
354
+ ╚════════════════════════════════════════════════════════════╝
355
+ """
356
+ )
357
+ fallback["simple"] = (
358
+ "👋 Chào mừng bạn đến với Vnstock! "
359
+ "Tài liệu: https://vnstocks.com/onboard | "
360
+ "Cộng đồng: https://facebook.com/groups/vnstock.official"
361
+ )
362
+ return fallback
363
+
364
+ # Singleton instance for module-level use
365
+ manager = ContentManager()
366
+
367
+ def present(context: str = "init", ad_category: int = AdCategory.FREE) -> None:
368
+ """
369
+ Shortcut to ContentManager.present_content for external callers.
370
+
371
+ Args:
372
+ context: propagate context string to ContentManager.
373
+ ad_category: loại quảng cáo (FREE, ANNOUNCEMENT, ...)
374
+ """
375
+ manager.present_content(context=context, ad_category=ad_category)