pkscreener 0.46.20250210.703__cp312-cp312-manylinux2014_x86_64.whl → 0.46.20250212.704__cp312-cp312-manylinux2014_x86_64.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.
Files changed (60) hide show
  1. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/README.txt +9 -7
  2. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/MarketMonitor.py +16 -5
  3. pkscreener-0.46.20250212.704.data/purelib/pkscreener/classes/__init__.py +1 -0
  4. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/globals.py +3 -3
  5. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/pkscreenerbot.py +285 -210
  6. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/requirements.txt +1 -1
  7. {pkscreener-0.46.20250210.703.dist-info → pkscreener-0.46.20250212.704.dist-info}/METADATA +12 -10
  8. pkscreener-0.46.20250212.704.dist-info/RECORD +58 -0
  9. pkscreener-0.46.20250210.703.data/purelib/pkscreener/classes/__init__.py +0 -1
  10. pkscreener-0.46.20250210.703.dist-info/RECORD +0 -58
  11. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/Disclaimer.txt +0 -0
  12. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/LICENSE-Others.txt +0 -0
  13. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/LICENSE.txt +0 -0
  14. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/LogoWM.txt +0 -0
  15. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/__init__.py +0 -0
  16. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/ArtTexts.py +0 -0
  17. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/AssetsManager.py +0 -0
  18. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/Backtest.py +0 -0
  19. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/Barometer.py +0 -0
  20. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/BaseScreeningStatistics.py +0 -0
  21. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/CandlePatterns.py +0 -0
  22. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/Changelog.py +0 -0
  23. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/ConfigManager.py +0 -0
  24. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/ConsoleMenuUtility.py +0 -0
  25. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/ConsoleUtility.py +0 -0
  26. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/Fetcher.py +0 -0
  27. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/GlobalStore.py +0 -0
  28. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/ImageUtility.py +0 -0
  29. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/MarketStatus.py +0 -0
  30. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/MenuOptions.py +0 -0
  31. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/Messenger.py +0 -0
  32. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/OtaUpdater.py +0 -0
  33. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKAnalytics.py +0 -0
  34. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKDataService.py +0 -0
  35. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKDemoHandler.py +0 -0
  36. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKMarketOpenCloseAnalyser.py +0 -0
  37. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKPremiumHandler.py +0 -0
  38. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKScanRunner.py +0 -0
  39. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKScheduledTaskProgress.py +0 -0
  40. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKScheduler.py +0 -0
  41. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKSpreadsheets.py +0 -0
  42. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKTask.py +0 -0
  43. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PKUserRegistration.py +0 -0
  44. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/Pktalib.py +0 -0
  45. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/Portfolio.py +0 -0
  46. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/PortfolioXRay.py +0 -0
  47. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/ScreeningStatistics.py +0 -0
  48. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/StockScreener.py +0 -0
  49. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/StockSentiment.py +0 -0
  50. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/UserMenuChoicesHandler.py +0 -0
  51. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/Utility.py +0 -0
  52. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/WorkflowManager.py +0 -0
  53. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/classes/keys.py +0 -0
  54. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/courbd.ttf +0 -0
  55. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/pkscreener.ini +0 -0
  56. {pkscreener-0.46.20250210.703.data → pkscreener-0.46.20250212.704.data}/purelib/pkscreener/pkscreenercli.py +0 -0
  57. {pkscreener-0.46.20250210.703.dist-info → pkscreener-0.46.20250212.704.dist-info}/LICENSE +0 -0
  58. {pkscreener-0.46.20250210.703.dist-info → pkscreener-0.46.20250212.704.dist-info}/WHEEL +0 -0
  59. {pkscreener-0.46.20250210.703.dist-info → pkscreener-0.46.20250212.704.dist-info}/entry_points.txt +0 -0
  60. {pkscreener-0.46.20250210.703.dist-info → pkscreener-0.46.20250212.704.dist-info}/top_level.txt +0 -0
@@ -102,6 +102,12 @@ from telegram.ext import (
102
102
  Filters,
103
103
  CallbackContext
104
104
  )
105
+ from PKDevTools.classes.Singleton import SingletonType, SingletonMixin
106
+
107
+ class PKLocalCache(SingletonMixin, metaclass=SingletonType):
108
+ def __init__(self):
109
+ super(PKLocalCache, self).__init__()
110
+ self.registeredIDs = []
105
111
 
106
112
  # Enable logging
107
113
  logging.basicConfig(
@@ -137,6 +143,8 @@ _updater = None
137
143
 
138
144
  TOP_LEVEL_SCANNER_MENUS = ["X", "B", "MI","DV", "P"] #
139
145
  TOP_LEVEL_SCANNER_SKIP_MENUS = ["M", "S", "F", "G", "C", "T", "D", "I", "E", "U", "L", "Z", "P"] # Last item will be skipped.
146
+ TOP_LEVEL_MARKUP_SKIP_MENUS = TOP_LEVEL_SCANNER_SKIP_MENUS[:len(TOP_LEVEL_SCANNER_SKIP_MENUS)-1]
147
+ TOP_LEVEL_MARKUP_SKIP_MENUS.extend(["X","P","B"])
140
148
  INDEX_SKIP_MENUS_1_To_4 = ["W","E","M","Z","0","5","6","7","8","9","10","11","12","13","14","S","15"]
141
149
  INDEX_SKIP_MENUS_5_TO_9 = ["W","E","M","Z","N","0","1","2","3","4","10","11","12","13","14","S","15"]
142
150
  INDEX_SKIP_MENUS_10_TO_15 = ["W","E","M","Z","N","0","1","2","3","4","5","6","7","8","9","S"]
@@ -158,6 +166,21 @@ PIPED_SCAN_SKIP_INDEX_MENUS =["W","N","E","S","0","Z","M","15"]
158
166
  UNSUPPORTED_COMMAND_MENUS =["22","M","Z","0",str(MAX_MENU_OPTION)]
159
167
  SUPPORTED_COMMAND_MENUS = ["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45"]
160
168
 
169
+ def registerUser(user,forceFetch=False):
170
+ otpValue, subsModel,subsValidity,alertUser = 0,0,None,None
171
+ if user is not None and (user.id not in PKLocalCache().registeredIDs or forceFetch):
172
+ dbManager = DBManager()
173
+ otpValue, subsModel,subsValidity,alertUser = dbManager.getOTP(user.id,user.username,f"{user.first_name} {user.last_name}",validityIntervalInSeconds=configManager.otpInterval)
174
+ if str(otpValue).strip() != '0' and user.id not in PKLocalCache().registeredIDs:
175
+ PKLocalCache().registeredIDs.append(alertUser.userid)
176
+ return otpValue, subsModel,subsValidity,alertUser
177
+
178
+ def loadRegisteredUsers():
179
+ dbManager = DBManager()
180
+ users = dbManager.getUsers(fieldName="userid")
181
+ userIDs = [user.userid for user in users]
182
+ PKLocalCache().registeredIDs.extend(userIDs)
183
+
161
184
  def initializeIntradayTimer():
162
185
  try:
163
186
  if (not PKDateUtilities.isTodayHoliday()[0]):
@@ -249,65 +272,17 @@ def matchUTR(update: Update, context: CallbackContext) -> str:
249
272
  updatedResults = "We could not find any transaction details with the provided UTR.\nUPI transaction reference number is a 12-digit alphanumeric/numeric code that serves as a unique identifier for transactions. It is also known as the Unique Transaction Reference (UTR) number.\nYou can find your UPI reference number in the UPI-enabled app you used to make the transaction.\nFor example, you can find your UPI reference number in the History section of Google Pay. \nIn the Paytm app, you can find it by clicking View Details.\n\nIf you still cannot find it, please drop a message with transaction details/snapshot to @ItsOnlyPK to enable subscription."
250
273
  else:
251
274
  updatedResults = "Did you forget to include the UTR number with /Check ?\nYou should use it like this:\n\n/Check UTR_Here\n\nUPI transaction reference number is a 12-digit alphanumeric/numeric code that serves as a unique identifier for transactions. It is also known as the Unique Transaction Reference (UTR) number.\nYou can find your UPI reference number in the UPI-enabled app you used to make the transaction.\nFor example, you can find your UPI reference number in the History section of Google Pay. \nIn the Paytm app, you can find it by clicking View Details.\n\nIf you still cannot find it, please drop a message with transaction details/snapshot to @ItsOnlyPK to enable subscription."
252
- update.message.reply_text(sanitiseTexts(updatedResults), parse_mode="HTML")
275
+ update.message.reply_text(sanitiseTexts(updatedResults), reply_markup=default_markup(user=user),parse_mode="HTML")
253
276
  shareUpdateWithChannel(update=update, context=context, optionChoices=f"/otp\n{updatedResults}")
254
277
  return START_ROUTES
255
278
 
256
- def otp(update: Update, context: CallbackContext) -> str:
257
- global bot_available
258
- updateCarrier = None
259
- if update is None:
260
- return
261
- else:
262
- if update.callback_query is not None:
263
- updateCarrier = update.callback_query
264
- if update.message is not None:
265
- updateCarrier = update.message
266
- if updateCarrier is None:
267
- return
268
- # Get user that sent /start and log his name
269
- user = updateCarrier.from_user
270
- logger.info("User %s started the conversation.", user.first_name)
271
- if not bot_available:
272
- # Sometimes, either the payment does not go through or
273
- # it takes time to process the last month's payment if
274
- # done in the past 24 hours while the last date was today.
275
- # If that happens, we won't be able to run bots or scanners
276
- # without incurring heavy charges. Let's run in the
277
- # unavailable mode instead until this gets fixed.
278
- updatedResults = APOLOGY_TEXT
279
-
280
- if bot_available:
281
- try:
282
- otpValue = 0
283
- dbManager = DBManager()
284
- otpValue, subsModel,subsValidity = dbManager.getOTP(user.id,user.username,f"{user.first_name} {user.last_name}",validityIntervalInSeconds=configManager.otpInterval)
285
- except Exception as e: # pragma: no cover
286
- logger.error(e)
287
- pass
288
- userText = f"\nUserID: <b>{user.id}</b>"
289
- try:
290
- subscriptionModelNames = "\n<pre>Following basic and premium subscription models are available. Premium subscription allows for unlimited premium scans:\n"
291
- for name,value in PKUserSusbscriptions().subscriptionKeyValuePairs.items():
292
- if name == PKSubscriptionModel.No_Subscription.name:
293
- subscriptionModelNames = f"{subscriptionModelNames}\n{name} : ₹ {value} (Only Basic Scans are free)\n"
294
- else:
295
- subscriptionModelNames = f"{subscriptionModelNames}\n{name.ljust(15)} : ₹ {value}"
296
- subscriptionModelNames = f"{subscriptionModelNames}</pre>\nPlease pay to subscribe:\n\n1. Using UPI(India) to <b>PKScreener@APL</b> \nor\n2. Proudly <b>sponsor</b>: https://github.com/sponsors/pkjmesra?frequency=recurring&sponsor=pkjmesra\n\nPlease drop a message to @ItsOnlyPK on Telegram after paying to enable subscription manually or use \n\n/check UPI_UTR_HERE_After_Making_Payment to share transaction reference number to automatically enable subscription after making payment via UPI\n!"
279
+ def editMessageText(query,editedText,reply_markup):
280
+ editedText = f"{PKDateUtilities.currentDateTime()}:\n{editedText}"
281
+ if query is not None and hasattr(query, "edit_message_text"):
282
+ query.edit_message_text(text=editedText, reply_markup=reply_markup,parse_mode="HTML")
297
283
 
298
- subscriptionModelName = PKUserSusbscriptions().subscriptionValueKeyPairs[subsModel]
299
- if subscriptionModelName != PKSubscriptionModel.No_Subscription.name:
300
- subscriptionModelName = f"{subscriptionModelName} (Expires on: {subsValidity})"
301
- except Exception as e:
302
- logger.error(e)
303
- subscriptionModelName = PKSubscriptionModel.No_Subscription.name
304
- pass
305
- if otpValue == 0:
306
- updatedResults = f"We are having difficulty generating OTP for your {userText}. Please try again later."
307
- else:
308
- updatedResults = f"Please use your {userText} \nwith the following OTP to login to PKScreener:\n<b>{otpValue}</b>\n\nYour current subscription : <b>{subscriptionModelName}</b>. {subscriptionModelNames}"
309
- update.message.reply_text(sanitiseTexts(updatedResults), parse_mode="HTML")
310
- shareUpdateWithChannel(update=update, context=context, optionChoices=f"/otp\n{updatedResults}")
284
+ def otp(update: Update, context: CallbackContext) -> str:
285
+ viewSubscriptionOptions(update,context,sendOTP=True)
311
286
  return START_ROUTES
312
287
 
313
288
  def start(update: Update, context: CallbackContext, updatedResults=None, monitorIndex=0,chosenBotMenuOption="") -> str:
@@ -339,32 +314,7 @@ def start(update: Update, context: CallbackContext, updatedResults=None, monitor
339
314
  # The keyboard is a list of button rows, where each row is in turn
340
315
  # a list (hence `[[...]]`).
341
316
  if bot_available:
342
- mns = m0.renderForMenu(asList=True)
343
- if (PKDateUtilities.isTradingTime() and not PKDateUtilities.isTodayHoliday()[0]) or ("PKDevTools_Default_Log_Level" in os.environ.keys()) or sys.argv[0].endswith(".py"):
344
- mns.append(menu().create(f"MI_{monitorIndex}", "👩‍💻 🚀 Intraday Monitor", 2))
345
- if user.username == OWNER_USER:
346
- mns.append(menu().create(f"DV_0", ("✅ Enable Logging" if not configManager.logsEnabled else "🚫 Disable Logging"), 2))
347
- mns.append(menu().create(f"DV_1", "🔄 Restart Bot", 2))
348
-
349
- inlineMenus = []
350
- keyboard = []
351
- rowIndex = 0
352
- iconDict = {"X":"🕵️‍♂️ 🔍 ","B":"📈 🎯 ","P":"🧨 💥 ","MI":"","DV":""}
353
- for mnu in mns:
354
- if mnu.menuKey[0:2] in TOP_LEVEL_SCANNER_MENUS:
355
- rowIndex +=1
356
- inlineMenus.append(
357
- InlineKeyboardButton(
358
- iconDict.get(str(mnu.menuKey[0:2])) + mnu.menuText.split("(")[0],
359
- callback_data="C" + str(mnu.menuKey),
360
- )
361
- )
362
- if rowIndex % 2 == 0:
363
- keyboard.append(inlineMenus)
364
- inlineMenus = []
365
- if len(inlineMenus) > 0:
366
- keyboard.append(inlineMenus)
367
- reply_markup = InlineKeyboardMarkup(keyboard)
317
+ reply_markup = default_markup(user=user,monitorIndex=monitorIndex)
368
318
  cmds = m0.renderForMenu(
369
319
  selectedMenu=None,
370
320
  skip=TOP_LEVEL_SCANNER_SKIP_MENUS[:len(TOP_LEVEL_SCANNER_SKIP_MENUS)-1],
@@ -375,22 +325,23 @@ def start(update: Update, context: CallbackContext, updatedResults=None, monitor
375
325
  reply_markup = None
376
326
 
377
327
  if updatedResults is None:
378
- cmdText = "\n/otp to generate an OTP to login to PKScreener desktop console\n\n/check UPI_UTR_HERE_After_Making_Payment to share transaction reference number to automatically enable subscription after making payment via UPI"
328
+ cmdText = "\n/otp : To generate an OTP to login to PKScreener desktop console\n/check UPI_UTR_HERE_After_Making_Payment : To share transaction reference number to automatically enable subscription after making payment via UPI\n"
379
329
  for cmd in cmds:
380
- cmdText = f"{cmdText}\n\n{cmd.commandTextKey()} for {cmd.commandTextLabel()}"
381
- tosDisclaimerText = "By using this Software, you agree to\n[+] having read through the Disclaimer (https://pkjmesra.github.io/PKScreener/Disclaimer.txt)\n[+] and accept Terms Of Service (https://pkjmesra.github.io/PKScreener/tos.txt) of PKScreener.\n\n[+] If that is not the case, you MUST immediately terminate using PKScreener and exit now!\n\n"
382
- menuText = f"Welcome {user.first_name}, {(user.username)}!\n\n{tosDisclaimerText}Please choose a menu option by selecting a button from below.\n\nYou can also explore a wide variety of all other scanners by typing in \n{cmdText}\n\n OR just use the buttons below to choose."
330
+ if cmd.menuKey not in TOP_LEVEL_MARKUP_SKIP_MENUS:
331
+ cmdText = f"{cmdText}\n{cmd.commandTextKey()} : {cmd.commandTextLabel()}"
332
+ tosDisclaimerText = "By using this Software, you agree to\n[+] having read through the <a href='https://pkjmesra.github.io/PKScreener/Disclaimer.txt'>Disclaimer</a>\n[+] and accept <a href='https://pkjmesra.github.io/PKScreener/tos.txt'>Terms Of Service</a> of PKScreener.\n[+] If that is not the case, you MUST immediately terminate using PKScreener and exit now!\n"
333
+ menuText = f"Welcome {user.first_name}, {(user.username)}!\n{tosDisclaimerText}Please choose a menu option by selecting a button from below.{cmdText}"
383
334
  try:
384
335
  if updateCarrier is not None and hasattr(updateCarrier, "data") and updateCarrier.data is not None and updateCarrier.data == "CP":
385
- menuText = f"Piped Scanners are available using /P . Click on this /P to begin using piped scanners. To use other scanners, choose a menu option by selecting a button from below.\n\nYou can also explore a wide variety of all other scanners by typing in \n{cmdText}\n\n OR just use the buttons below to choose."
336
+ menuText = f"Piped Scanners are available using /P . Click on this /P to begin using piped scanners. To use other scanners, choose a menu option by selecting a button from below.\n{cmdText}"
386
337
  except Exception as e: # pragma: no cover
387
338
  logger.error(e)
388
339
  pass
389
- menuText = f"{menuText}\n\nClick /start if you want to restart the session."
340
+ menuText = f"{menuText}\nClick /start if you want to restart the session."
390
341
  else:
391
342
  if not isUserSubscribed(user):
392
- updatedResults = f"Thank you for choosing Intraday Monitor!\n\nThis scan request is, however ,protected and is only available to premium subscribers. It seems like you are not subscribed to the paid/premium subscription to PKScreener.\nPlease checkout all premium options by sending out a request:\n\n/OTP\n\nFor basic/unpaid users, you can try out the following:\n /X_0 StockCode1,StockCode2,etc.\n/X_N\n/X_1\n"
393
- updatedResults = f"{updatedResults}\n\nClick /start if you want to restart the session."
343
+ updatedResults = f"Thank you for choosing Intraday Monitor!\n\nThis scan request is, however, protected and is only available to premium subscribers. It seems like you are not subscribed to the paid/premium subscription to PKScreener.\nPlease checkout all premium options by sending out a request:\n/OTP\n\nFor basic/unpaid users, you can try out the following:\n/X_0 StockCode1,StockCode2,etc.\n/X_N\n/X_1"
344
+ updatedResults = f"{updatedResults}\nClick /start if you want to restart the session."
394
345
  chosenBotMenuOption = f"{chosenBotMenuOption}\nInt. Monitor. MonitorIndex:{monitorIndex}\n{updatedResults}"
395
346
  menuText = updatedResults
396
347
  # Send message with text and appended InlineKeyboard
@@ -402,6 +353,7 @@ def start(update: Update, context: CallbackContext, updatedResults=None, monitor
402
353
  update.message.reply_text(
403
354
  sanitiseTexts(menuText),
404
355
  reply_markup=reply_markup,
356
+ parse_mode="HTML"
405
357
  )
406
358
  if Channel_Id is not None and len(str(Channel_Id)) > 0:
407
359
  context.bot.send_message(
@@ -409,7 +361,7 @@ def start(update: Update, context: CallbackContext, updatedResults=None, monitor
409
361
  text=f"Name: {user.first_name}, Username:@{user.username} with ID: {str(user.id)} started using the bot!\n{chosenBotMenuOption}",
410
362
  parse_mode="HTML",
411
363
  )
412
- DBManager().getOTP(user.id,user.username,f"{user.first_name} {user.last_name}",validityIntervalInSeconds=configManager.otpInterval)
364
+ registerUser(user)
413
365
  # Tell ConversationHandler that we're in state `FIRST` now
414
366
  return START_ROUTES
415
367
 
@@ -583,11 +535,74 @@ def PScanners(update: Update, context: CallbackContext) -> str:
583
535
  )
584
536
  keyboard = [inlineMenus]
585
537
  reply_markup = InlineKeyboardMarkup(keyboard)
586
- if query.message.text == menuText:
587
- menuText = f"{PKDateUtilities.currentDateTime()}:\n{menuText}"
588
- menuText = f"{menuText}\n\nClick /start if you want to restart the session."
589
- query.edit_message_text(text=menuText, reply_markup=reply_markup)
590
- DBManager().getOTP(user.id,user.username,f"{user.first_name} {user.last_name}",validityIntervalInSeconds=configManager.otpInterval)
538
+ menuText = f"{menuText}\nClick /start if you want to restart the session."
539
+ editMessageText(query=query,editedText=menuText,reply_markup=reply_markup)
540
+ registerUser(user)
541
+ return START_ROUTES
542
+
543
+ def viewSubscriptionOptions(update:Update,context:CallbackContext,sendOTP=False):
544
+ global bot_available
545
+ updateCarrier = None
546
+ updatedResults= ""
547
+ if update is None:
548
+ return
549
+ else:
550
+ if update.callback_query is not None:
551
+ updateCarrier = update.callback_query
552
+ if update.message is not None:
553
+ updateCarrier = update.message
554
+ if updateCarrier is None:
555
+ return
556
+ # Get user that sent /start and log his name
557
+ user = updateCarrier.from_user
558
+ logger.info("User %s started the conversation.", user.first_name)
559
+ if not bot_available:
560
+ # Sometimes, either the payment does not go through or
561
+ # it takes time to process the last month's payment if
562
+ # done in the past 24 hours while the last date was today.
563
+ # If that happens, we won't be able to run bots or scanners
564
+ # without incurring heavy charges. Let's run in the
565
+ # unavailable mode instead until this gets fixed.
566
+ updatedResults = APOLOGY_TEXT
567
+
568
+ if bot_available:
569
+ try:
570
+ otpValue = 0
571
+ alertUser = None
572
+ dbManager = DBManager()
573
+ otpValue, subsModel,subsValidity,alertUser = registerUser(user,forceFetch=True)
574
+ except Exception as e: # pragma: no cover
575
+ logger.error(e)
576
+ pass
577
+ userText = f"<b>UserID</b> : <code>{user.id}</code>"
578
+ try:
579
+ subscriptionModelNames = "\n<pre>Following basic and premium subscription models are available. Premium subscription allows for unlimited premium scans:\n"
580
+ for name,value in PKUserSusbscriptions().subscriptionKeyValuePairs.items():
581
+ if name == PKSubscriptionModel.No_Subscription.name:
582
+ subscriptionModelNames = f"{subscriptionModelNames}\n₹ {str(value).ljust(6)}: {name} (Only Basic Scans are free)\n"
583
+ else:
584
+ subscriptionModelNames = f"{subscriptionModelNames}\n₹ {str(value).ljust(6)}: {name}"
585
+ subscriptionModelNames = f"{subscriptionModelNames}</pre>\nPlease pay to subscribe:\n1. Using UPI(India) to <a href='https://tinyurl.com/v7h3t233'>PKScreener@APL</a> or\n2. Proudly <a href='https://github.com/sponsors/pkjmesra?frequency=recurring&sponsor=pkjmesra'><b>sponsor</b></a>\n\nPlease drop a message to @ItsOnlyPK on Telegram after paying to enable subscription manually or use \n\n/check UPI_UTR_HERE_After_Making_Payment to share transaction reference number to automatically enable subscription after making payment via UPI\n"
586
+
587
+ subscriptionModelName = PKUserSusbscriptions().subscriptionValueKeyPairs[subsModel]
588
+ if subscriptionModelName != PKSubscriptionModel.No_Subscription.name:
589
+ subscriptionModelName = f"{subscriptionModelName} (Expires on: {subsValidity})"
590
+ except Exception as e:
591
+ logger.error(e)
592
+ subscriptionModelName = PKSubscriptionModel.No_Subscription.name
593
+ pass
594
+ if sendOTP:
595
+ if otpValue == 0:
596
+ updatedResults = f"We are having difficulty generating OTP for your {userText}. Please try again later or reach out to @ItsOnlyPK."
597
+ else:
598
+ updatedResults = f"Please use the following to login to PKScreener:\n{userText}\n<b>OTP</b> : <code>{otpValue}</code>\n\nCurrent subscription : <b>{subscriptionModelName}</b>.\nCurrent alerts balance: <b>₹ {alertUser.balance if alertUser is not None else 0}</b>. {subscriptionModelNames}"
599
+ else:
600
+ updatedResults = f"Current subscription: <b>{subscriptionModelName}</b>.\nCurrent alerts balance: <b>₹ {alertUser.balance if alertUser is not None else 0}</b>. {subscriptionModelNames}"
601
+ if hasattr(updateCarrier, "reply_text"):
602
+ updateCarrier.reply_text(text=sanitiseTexts(updatedResults), reply_markup=default_markup(user=user),parse_mode="HTML")
603
+ elif hasattr(updateCarrier, "edit_message_text"):
604
+ editMessageText(query=updateCarrier,editedText=sanitiseTexts(updatedResults),reply_markup=default_markup(user=user))
605
+ shareUpdateWithChannel(update=update, context=context, optionChoices=f"/otp\n{updatedResults}")
591
606
  return START_ROUTES
592
607
 
593
608
  def subscribeToScannerAlerts(update: Update, context: CallbackContext) -> str:
@@ -622,11 +637,11 @@ def subscribeToScannerAlerts(update: Update, context: CallbackContext) -> str:
622
637
  return START_ROUTES
623
638
  dbManager = DBManager()
624
639
  alertUser = dbManager.alertsForUser(int(user.id))
625
- inlineMenus = []
626
640
  query.answer()
627
641
  menuText = ""
628
642
  requiredBalance = 40 if str(scanId).upper().startswith("P") else 31
629
- payWall = "Please pay to subscribe:\n\n1. Using UPI(India) to PKScreener@APL \nor\n2. Proudly sponsor: https://github.com/sponsors/pkjmesra?frequency=recurring&sponsor=pkjmesra\n\nPlease drop a message to @ItsOnlyPK along with UTR and Scan details on Telegram after paying to enable subscription manually or use \n\n/check UPI_UTR_HERE_After_Making_Payment to share transaction reference number to automatically update your balance after making payment via UPI\n! After that you can try re-subscribing!"
643
+ # upi://pay?pa=PKScreener@APL&pn=PKScreener&cu=INR
644
+ payWall = "Please pay to subscribe:\n1. Using UPI(India) to <a href='https://tinyurl.com/v7h3t233'>PKScreener@APL</a> or\n2. Proudly <a href='https://github.com/sponsors/pkjmesra?frequency=recurring&sponsor=pkjmesra'>sponsor</a>\n\nPlease drop a message to @ItsOnlyPK along with UTR and Scan details on Telegram after paying to enable subscription manually or use \n\n/check UPI_UTR_HERE_After_Making_Payment to share transaction reference number to automatically update your balance after making payment via UPI.\nAfter that you can try re-subscribing!"
630
645
  if alertUser is not None and alertUser.balance >= 0:
631
646
  # User has some balance
632
647
  if len(alertUser.scannerJobs) > 0:
@@ -636,7 +651,7 @@ def subscribeToScannerAlerts(update: Update, context: CallbackContext) -> str:
636
651
  else:
637
652
  if alertUser.balance < requiredBalance:
638
653
  # Insufficient balance
639
- menuText = f"You need at least {requiredBalance} to subscribe to {scanId} ! Your current balance {alertUser.balance} is insufficient. {payWall}"
654
+ menuText = f"You need at least <b>₹ {requiredBalance}</b> to subscribe to <b>{scanId} alerts for a day</b> ! Your current balance <b>₹ {alertUser.balance}</b> is <b>insufficient</b>. {payWall}"
640
655
  else:
641
656
  # Sufficient balance to subscribe to scanId
642
657
  subscribed = dbManager.updateAlertSubscriptionModel(user.id,requiredBalance,scanId)
@@ -647,19 +662,10 @@ def subscribeToScannerAlerts(update: Update, context: CallbackContext) -> str:
647
662
 
648
663
  elif alertUser is None or alertUser.balance == 0:
649
664
  # Either user is not subscribed or has 0 balance
650
- menuText = f"You need at least {requiredBalance} to subscribe to {scanId} ! {payWall}"
665
+ menuText = f"You need at least <b>₹ {requiredBalance}</b> to subscribe to <b>{scanId} alerts for a day</b> ! Your current balance <b>₹ 0</b> is <b>insufficient</b>. {payWall}"
651
666
 
652
- inlineMenus.append(
653
- InlineKeyboardButton(
654
- "Start", callback_data="start"
655
- )
656
- )
657
- keyboard = [inlineMenus]
658
- reply_markup = InlineKeyboardMarkup(keyboard)
659
- if query.message.text == menuText:
660
- menuText = f"{PKDateUtilities.currentDateTime()}:\n{menuText}"
661
- menuText = f"{menuText}\n\nClick /start if you want to restart the session."
662
- query.edit_message_text(text=menuText, reply_markup=reply_markup)
667
+ menuText = f"{menuText}\nClick /start if you want to restart the session."
668
+ editMessageText(query=query,editedText=sanitiseTexts(menuText),reply_markup=default_markup(user=user))
663
669
  return START_ROUTES
664
670
 
665
671
 
@@ -756,11 +762,9 @@ def XScanners(update: Update, context: CallbackContext) -> str:
756
762
  )
757
763
  keyboard = [inlineMenus]
758
764
  reply_markup = InlineKeyboardMarkup(keyboard)
759
- if query.message.text == menuText:
760
- menuText = f"{PKDateUtilities.currentDateTime()}:\n{menuText}"
761
- menuText = f"{menuText}\n\nClick /start if you want to restart the session."
762
- query.edit_message_text(text=menuText, reply_markup=reply_markup)
763
- DBManager().getOTP(user.id,user.username,f"{user.first_name} {user.last_name}",validityIntervalInSeconds=configManager.otpInterval)
765
+ menuText = f"{menuText}\nClick /start if you want to restart the session."
766
+ editMessageText(query=query,editedText=sanitiseTexts(menuText),reply_markup=reply_markup)
767
+ registerUser(user)
764
768
  return START_ROUTES
765
769
 
766
770
  def getinlineMenuListRow(keyboardRows=[]):
@@ -817,7 +821,7 @@ def Level2(update: Update, context: CallbackContext) -> str:
817
821
  start(update, context)
818
822
  return START_ROUTES
819
823
 
820
- reply_markup = default_markup([])
824
+ reply_markup = default_markup(user=user)
821
825
  if (len(selection) == 2 and selection[0] in ["X","B"] and selection[1] in ["P1","P2","P3"]) or \
822
826
  (len(selection) == 4 and selection[0] in ["P"] and selection[3] in ["P1","P2","P3"]): # Piped scan index options
823
827
  nextOption = ""
@@ -862,10 +866,8 @@ def Level2(update: Update, context: CallbackContext) -> str:
862
866
 
863
867
  keyboard = keyboardRows
864
868
  reply_markup = InlineKeyboardMarkup(keyboard)
865
- if query.message.text == menuText:
866
- menuText = f"{PKDateUtilities.currentDateTime()}:\n{menuText}"
867
- menuText = f"{menuText}\n\nClick /start if you want to restart the session."
868
- query.edit_message_text(text=menuText, reply_markup=reply_markup)
869
+ menuText = f"{menuText}\nClick /start if you want to restart the session."
870
+ editMessageText(query=query,editedText=sanitiseTexts(menuText),reply_markup=reply_markup)
869
871
  return START_ROUTES
870
872
  if len(selection) == 2 or (len(selection) == 3 and selection[2] == "P"):
871
873
  if str(selection[1]).isnumeric():
@@ -986,7 +988,7 @@ def Level2(update: Update, context: CallbackContext) -> str:
986
988
  )
987
989
  mns.append(menu().create("H", "Home", 2))
988
990
  mns.append(menu().create("P", "More Options", 2))
989
- elif str(selection[2]).isnumeric():
991
+ elif str(selection[2]).isnumeric() and selection[0].lower() not in ["p"]:
990
992
  preSelection = f"{selection[0]}_{selection[1]}_{selection[2]}"
991
993
  if selection[2] in SCANNER_MENUS_WITH_SUBMENU_SUPPORT:
992
994
  menuText = m3.renderForMenu(
@@ -1001,7 +1003,7 @@ def Level2(update: Update, context: CallbackContext) -> str:
1001
1003
  skip=["0","M","Z"],
1002
1004
  )
1003
1005
  menuText = f"{menuText}\n\nH > Home"
1004
- menuText = f"{menuText}\n\nClick /start if you want to restart the session."
1006
+ menuText = f"{menuText}\nClick /start if you want to restart the session."
1005
1007
  mns.append(menu().create("H", "Home", 2))
1006
1008
  else:
1007
1009
  if selection[2] == "4": # Last N days
@@ -1030,7 +1032,7 @@ def Level2(update: Update, context: CallbackContext) -> str:
1030
1032
  )
1031
1033
  keyboard = keyboardRows
1032
1034
  reply_markup = InlineKeyboardMarkup(keyboard)
1033
- elif len(selection) >= 4:
1035
+ if (len(selection) >= 4 and selection[0].lower() not in ["p"]) or (len(selection) >= 3 and selection[0].lower() in ["p"]):
1034
1036
  if len(selection) == 4:
1035
1037
  if selection[2] in SCANNER_SUBMENUS_CHILDLEVEL_SUPPORT.keys() and selection[3] in SCANNER_SUBMENUS_CHILDLEVEL_SUPPORT[selection[2]]:
1036
1038
  m0.renderForMenu(
@@ -1079,7 +1081,7 @@ def Level2(update: Update, context: CallbackContext) -> str:
1079
1081
  )
1080
1082
  menuText = f"{menuText}\n\nH > Home"
1081
1083
  mns.append(menu().create("H", "Home", 3))
1082
- menuText = f"{menuText}\n\nClick /start if you want to restart the session."
1084
+ menuText = f"{menuText}\nClick /start if you want to restart the session."
1083
1085
  if mns is not None:
1084
1086
  for mnu in mns:
1085
1087
  activeInlineRow = getinlineMenuListRow(keyboardRows)
@@ -1088,8 +1090,9 @@ def Level2(update: Update, context: CallbackContext) -> str:
1088
1090
  reply_markup = InlineKeyboardMarkup(keyboard)
1089
1091
  if len(mns) == 0:
1090
1092
  menuText = ''
1091
- elif len(selection) > 4:
1092
- if selection[0] in 'P' and len(selection[3]) == 0:
1093
+ elif len(selection) > 4 or (len(selection) >= 3 and selection[0].lower() in ["p"]):
1094
+ if (selection[0] in 'P' and ((len(selection) >= 4 and len(selection[3]) == 0) or (len(selection) == 3 and str(selection[2]).isnumeric()))):
1095
+ preSelection = query.data.upper().replace("C", "")
1093
1096
  skipMenus = ["N"]
1094
1097
  skipMenus.extend(INDEX_SKIP_MENUS_1_To_4)
1095
1098
  # Create the menu text labels
@@ -1126,7 +1129,7 @@ def Level2(update: Update, context: CallbackContext) -> str:
1126
1129
  )
1127
1130
  keyboard = [inlineMenus]
1128
1131
  reply_markup = InlineKeyboardMarkup(keyboard)
1129
- else:
1132
+ elif len(mns) == 0:
1130
1133
  menuText = ''
1131
1134
 
1132
1135
  if menuText is None or len(menuText) == 0:
@@ -1135,9 +1138,9 @@ def Level2(update: Update, context: CallbackContext) -> str:
1135
1138
  )
1136
1139
  optionChoices = f"{optionChoices}{f' > {selection[4]}' if len(selection) > 4 else ''}".replace(" > >","").strip()
1137
1140
  expectedTime = f"{'10 to 15' if '> 15' in optionChoices else '1 to 2'}"
1138
- menuText = f"Thank you for choosing {optionChoices.replace(' > > ','')}. You will receive the notification/results in about {expectedTime} minutes. It generally takes 1-2 minutes for NSE (2000+) stocks and 10-15 minutes for NASDAQ (7300+).\n\nPKScreener had been free for a long time, but owing to cost/budgeting issues, only a basic set of features will always remain free for everyone. Consider donating to help cover the basic server costs or subscribe to premium, if not subscribed yet:\n\nUPI (India): PKScreener@APL \n\nor\nhttps://github.com/sponsors/pkjmesra?frequency=recurring&sponsor=pkjmesra"
1141
+ menuText = f"Thank you for choosing {optionChoices.replace(' > > ','')}. You will receive the notification/results in about {expectedTime} minutes. It generally takes 1-2 minutes for NSE (2000+) stocks and 10-15 minutes for NASDAQ (7300+).\n\nPKScreener had been free for a long time, but owing to cost/budgeting issues, only a basic set of features will always remain free for everyone. Consider donating to help cover the basic server costs or subscribe to premium, if not subscribed yet:\n\nUPI (India): <a href='https://tinyurl.com/v7h3t233'>PKScreener@APL</a> \n\nor <a href='https://github.com/sponsors/pkjmesra?frequency=recurring&sponsor=pkjmesra'>sponsor</a>"
1139
1142
 
1140
- reply_markup = default_markup([])
1143
+ reply_markup = default_markup(user=user)
1141
1144
  options = ":".join(selection)
1142
1145
  shouldSendUpdate = launchScreener(
1143
1146
  options=options,
@@ -1147,7 +1150,7 @@ def Level2(update: Update, context: CallbackContext) -> str:
1147
1150
  update=update,
1148
1151
  )
1149
1152
  if not shouldSendUpdate:
1150
- DBManager().getOTP(user.id,user.username,f"{user.first_name} {user.last_name}",validityIntervalInSeconds=configManager.otpInterval)
1153
+ registerUser(user)
1151
1154
  return START_ROUTES
1152
1155
  try:
1153
1156
  if optionChoices != "" and Channel_Id is not None and len(str(Channel_Id)) > 0:
@@ -1164,20 +1167,54 @@ def Level2(update: Update, context: CallbackContext) -> str:
1164
1167
  sendUpdatedMenu(
1165
1168
  menuText=menuText, update=update, context=context, reply_markup=reply_markup
1166
1169
  )
1167
- DBManager().getOTP(user.id,user.username,f"{user.first_name} {user.last_name}",validityIntervalInSeconds=configManager.otpInterval)
1170
+ scanRequest = optionChoices.replace(" ", "").replace(">", "_").replace(":","_").replace("_D","").upper()
1171
+ sendSubscriptionOption(update,context,scanRequest)
1172
+ registerUser(user)
1168
1173
  return START_ROUTES
1169
1174
 
1170
- def default_markup(inlineMenus):
1171
- mns = m0.renderForMenu(asList=True)
1175
+ def default_markup(user=None,monitorIndex=0):
1176
+ mns = m0.renderForMenu(selectedMenu=None,
1177
+ skip=TOP_LEVEL_SCANNER_SKIP_MENUS[:len(TOP_LEVEL_SCANNER_SKIP_MENUS)-1],
1178
+ asList=True,
1179
+ renderStyle=MenuRenderStyle.STANDALONE,
1180
+ )
1181
+ if (PKDateUtilities.isTradingTime() and not PKDateUtilities.isTodayHoliday()[0]) or ("PKDevTools_Default_Log_Level" in os.environ.keys()) or sys.argv[0].endswith(".py"):
1182
+ mns.append(menu().create(f"MI_{monitorIndex}", "👩‍💻 🚀 Intraday Monitor", 2))
1183
+ if user is not None and user.username == OWNER_USER:
1184
+ mns.append(menu().create(f"DV_0", ("✅ Enable Logging" if not configManager.logsEnabled else "🚫 Disable Logging"), 2))
1185
+ mns.append(menu().create(f"DV_1", "🔄 Restart Bot", 2))
1186
+ keyboard = []
1187
+ inlineMenus = []
1188
+ lastRowMenus = []
1189
+ rowIndex = 0
1190
+ iconDict = {"X":"🕵️‍♂️ 🔍 ","B":"📈 🎯 ","P":"🧨 💥 ","MI":"","DV":"","VS":"🔔 📣 ","start":"🟢 🏁 "}
1172
1191
  for mnu in mns:
1173
- if mnu.menuKey in TOP_LEVEL_SCANNER_MENUS:
1192
+ if mnu.menuKey[0:2] in TOP_LEVEL_SCANNER_MENUS:
1193
+ rowIndex +=1
1174
1194
  inlineMenus.append(
1175
- InlineKeyboardButton(
1176
- mnu.menuText.split("(")[0],
1177
- callback_data="C" + str(mnu.menuKey),
1178
- )
1195
+ InlineKeyboardButton(
1196
+ iconDict.get(str(mnu.menuKey[0:2])) + mnu.menuText.split("(")[0],
1197
+ callback_data="C" + str(mnu.menuKey),
1179
1198
  )
1180
- keyboard = [inlineMenus]
1199
+ )
1200
+ if rowIndex % 2 == 0:
1201
+ keyboard.append(inlineMenus)
1202
+ inlineMenus = []
1203
+ lastRowMenus.append(
1204
+ InlineKeyboardButton(
1205
+ iconDict.get("VS") + "Subscriptions",
1206
+ callback_data="VS_",
1207
+ )
1208
+ )
1209
+ lastRowMenus.append(
1210
+ InlineKeyboardButton(
1211
+ iconDict.get("start") + "Start",
1212
+ callback_data="start",
1213
+ )
1214
+ )
1215
+ if len(inlineMenus) > 0:
1216
+ keyboard.append(inlineMenus)
1217
+ keyboard.append(lastRowMenus)
1181
1218
  reply_markup = InlineKeyboardMarkup(keyboard)
1182
1219
  return reply_markup
1183
1220
 
@@ -1185,14 +1222,8 @@ def default_markup(inlineMenus):
1185
1222
  def sendUpdatedMenu(menuText, update: Update, context, reply_markup, replaceWhiteSpaces=True):
1186
1223
  try:
1187
1224
  menuText.replace(" ", "").replace(" ", "").replace("\t", "").replace(colorText.FAIL,"").replace(colorText.END,"").replace(colorText.WHITE,"") if replaceWhiteSpaces else menuText
1188
- menuText = f"{menuText}\n\nClick /start if you want to restart the session." if "/start" not in menuText else menuText
1189
- if update.callback_query.message.text == menuText:
1190
- menuText = f"{PKDateUtilities.currentDateTime()}:\n{menuText}"
1191
- update.callback_query.edit_message_text(
1192
- text=menuText,
1193
- parse_mode="HTML",
1194
- reply_markup=reply_markup,
1195
- )
1225
+ menuText = f"{menuText}\nClick /start if you want to restart the session." if "/start" not in menuText else menuText
1226
+ editMessageText(query=update.callback_query,editedText=sanitiseTexts(menuText),reply_markup=reply_markup)
1196
1227
  except Exception as e:# pragma: no cover
1197
1228
  logger.log(e)
1198
1229
  start(update, context)
@@ -1213,16 +1244,14 @@ def launchScreener(options, user, context, optionChoices, update):
1213
1244
  isBasicScanRequest = True
1214
1245
  break
1215
1246
  if not isBasicScanRequest:
1216
- responseText = f"Thank you for choosing {scanRequest}!\n\nThis scan request is,however,protected and is only available to premium subscribers. It seems like you are not subscribed to the paid/premium subscription to PKScreener.\nPlease checkout all premium options by sending out a request:\n\n/OTP\n\nFor basic/unpaid users, you can try out the following:\n /X_0 StockCode1,StockCode2,etc.\n/X_N\n/X_1\n"
1247
+ responseText = f"Thank you for choosing {scanRequest}!\n\nThis {'Backtest' if str(scanRequest).startswith('B') else 'Scan'} request is, however, protected and is only available to premium subscribers. It seems like you are not subscribed to the paid/premium subscription to PKScreener.\nPlease checkout all premium options by sending out a request:\n\n/OTP\n\nFor basic/unpaid users, you can try out the following:\n/X_0 StockCode1,StockCode2,etc.\n/X_N\n/X_1\n"
1217
1248
  if update is not None and update.message is not None:
1218
- update.message.reply_text(sanitiseTexts(responseText))
1249
+ update.message.reply_text(sanitiseTexts(responseText),reply_markup=default_markup(user=user),parse_mode="HTML")
1219
1250
  else:
1220
- responseText = f"{responseText}\n\nClick /start if you want to restart the session."
1221
- update.callback_query.edit_message_text(
1222
- text=responseText,
1223
- reply_markup=default_markup([]),
1224
- )
1251
+ responseText = f"{responseText}\nClick /start if you want to restart the session."
1252
+ editMessageText(query=update.callback_query,editedText=sanitiseTexts(responseText),reply_markup=default_markup(user=user))
1225
1253
  shareUpdateWithChannel(update=update, context=context, optionChoices=responseText)
1254
+ sendSubscriptionOption(update,context,scanRequest)
1226
1255
  return False
1227
1256
 
1228
1257
  if str(optionChoices.upper()).startswith("B"):
@@ -1236,13 +1265,10 @@ def launchScreener(options, user, context, optionChoices, update):
1236
1265
  responseText = f"{responseText}\n\nStock-wise: https://pkjmesra.github.io/PKScreener/Backtest-Reports/PKScreener_{optionChoices}_backtest_result_StockSorted.html"
1237
1266
  responseText = f"{responseText}\n\nOther Reports: https://pkjmesra.github.io/PKScreener/BacktestReports.html"
1238
1267
  if update is not None and update.message is not None:
1239
- update.message.reply_text(sanitiseTexts(responseText))
1268
+ update.message.reply_text(sanitiseTexts(responseText),reply_markup=default_markup(user=user),parse_mode="HTML")
1240
1269
  else:
1241
- responseText = f"{responseText}\n\nClick /start if you want to restart the session."
1242
- update.callback_query.edit_message_text(
1243
- text=responseText,
1244
- reply_markup=default_markup([]),
1245
- )
1270
+ responseText = f"{responseText}\nClick /start if you want to restart the session."
1271
+ editMessageText(query=update.callback_query,editedText=sanitiseTexts(responseText),reply_markup=default_markup(user=user))
1246
1272
  shareUpdateWithChannel(
1247
1273
  update=update, context=context, optionChoices=optionChoices
1248
1274
  )
@@ -1309,15 +1335,35 @@ def BBacktests(update: Update, context: CallbackContext) -> str:
1309
1335
  ]
1310
1336
  reply_markup = InlineKeyboardMarkup(keyboard)
1311
1337
  responseText = "Backtesting NOT implemented yet in this Bot!\n\n\nYou can use backtesting by downloading the software from https://github.com/pkjmesra/PKScreener/"
1312
- responseText = f"{responseText}\n\nClick /start if you want to restart the session."
1313
- query.edit_message_text(
1314
- text=responseText,
1315
- reply_markup=reply_markup,
1316
- )
1317
- DBManager().getOTP(user.id,user.username,f"{user.first_name} {user.last_name}",validityIntervalInSeconds=configManager.otpInterval)
1338
+ responseText = f"{responseText}\nClick /start if you want to restart the session."
1339
+ editMessageText(query=query,editedText=sanitiseTexts(responseText),reply_markup=default_markup(user=user))
1340
+ registerUser(user)
1318
1341
  return START_ROUTES
1319
1342
 
1320
-
1343
+ def sendSubscriptionOption(update:Update,context:CallbackContext,scanId):
1344
+ updateCarrier = None
1345
+ if update is None:
1346
+ return
1347
+ else:
1348
+ if update.callback_query is not None:
1349
+ updateCarrier = update.callback_query
1350
+ if update.message is not None:
1351
+ updateCarrier = update.message
1352
+ if updateCarrier is None:
1353
+ return
1354
+ # Get user that sent /start and log his name
1355
+ user = updateCarrier.from_user
1356
+ reply_markup = {
1357
+ "inline_keyboard": [
1358
+ [{"text": f"Yes! Subscribe", "callback_data": f"SUB_{scanId}"}]
1359
+ ],
1360
+ }
1361
+ message=f"Would you like to subscribe to this (<b>{scanId}</b>) automated scan alert for a day during market hours (NSE - IST timezone)? You will need to pay <b>₹ {'40' if str(scanId).upper().startswith('P') else '31'} (One time per day)</b> for automated alerts to only <b>{scanId}</b> all day on the day of subscription."
1362
+ if len(str(scanId).strip()) > 0 and not str(scanId).startswith("B"):
1363
+ context.bot.send_message(
1364
+ chat_id=user.id, text=message, reply_markup=reply_markup, parse_mode="HTML"
1365
+ )
1366
+
1321
1367
  def end(update: Update, context: CallbackContext) -> str:
1322
1368
  """Returns `ConversationHandler.END`, which tells the
1323
1369
  ConversationHandler that the conversation is over.
@@ -1325,10 +1371,8 @@ def end(update: Update, context: CallbackContext) -> str:
1325
1371
  query = update.callback_query
1326
1372
  query.answer()
1327
1373
  responseText = "See https://github.com/pkjmesra/PKScreener/ for more details or join https://t.me/PKScreener. \n\n\nSee you next time!"
1328
- responseText = f"{responseText}\n\nClick /start if you want to restart the session."
1329
- query.edit_message_text(
1330
- text=responseText
1331
- )
1374
+ responseText = f"{responseText}\nClick /start if you want to restart the session."
1375
+ editMessageText(query=query,editedText=sanitiseTexts(responseText),reply_markup=default_markup(query.from_user))
1332
1376
  return ConversationHandler.END
1333
1377
 
1334
1378
 
@@ -1421,6 +1465,21 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1421
1465
  if not bot_available:
1422
1466
  start(update, context)
1423
1467
  return START_ROUTES
1468
+ updateCarrier = None
1469
+ if update is None:
1470
+ return
1471
+ else:
1472
+ if hasattr(update, "callback_query") and update.callback_query is not None:
1473
+ updateCarrier = update.callback_query
1474
+ if hasattr(update, "message") and update.message is not None:
1475
+ updateCarrier = update.message
1476
+ if hasattr(update, "effective_message") and update.effective_message is not None:
1477
+ updateCarrier = update.effective_message
1478
+ if updateCarrier is None:
1479
+ return
1480
+ # Get user that sent /start and log his name
1481
+ user = updateCarrier.from_user
1482
+
1424
1483
  msg = update.effective_message
1425
1484
  try:
1426
1485
  m = re.match(r"\s*/([0-9a-zA-Z_-]+)\s*(.*)", msg.text)
@@ -1451,7 +1510,7 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1451
1510
  cmdText = f"{cmdText}\n/F_0 STOCK_CODE1,STOCK_CODE2 To find which all scanners had these stock codes(Reverse look up)"
1452
1511
 
1453
1512
  if cmd.upper() in TOP_LEVEL_SCANNER_MENUS:
1454
- shareUpdateWithChannel(update=update, context=context)
1513
+ shareUpdateWithChannel(update=update, context=context,optionChoices=msg)
1455
1514
  m0.renderForMenu(
1456
1515
  selectedMenu=None,
1457
1516
  skip=TOP_LEVEL_SCANNER_SKIP_MENUS[:len(TOP_LEVEL_SCANNER_SKIP_MENUS)-1],
@@ -1474,8 +1533,8 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1474
1533
  if cmd in ["x"]:
1475
1534
  cmdText = f"{cmdText}\n\nFor option 0 <Screen stocks by the stock name>, please type in the command in the following format\n/X_0 SBIN\n or \n/X_0_0 SBIN\nand hit send where SBIN is the NSE stock code.For multiple stocks, you can type in \n/X_0 SBIN,ICICIBANK,OtherStocks\nYou can put in any number of stocks separated by space or comma(,)."
1476
1535
  """Send a message when the command /help is issued."""
1477
- cmdText = f"{cmdText}\n\nClick /start if you want to restart the session."
1478
- update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"))
1536
+ cmdText = f"{cmdText}\nClick /start if you want to restart the session."
1537
+ update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"),reply_markup=default_markup(user=user),parse_mode="HTML")
1479
1538
  return START_ROUTES
1480
1539
 
1481
1540
  if update.message is None:
@@ -1483,7 +1542,7 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1483
1542
  return START_ROUTES
1484
1543
 
1485
1544
  if "x_0" in cmd or "x_0_0" in cmd or "b_0" in cmd or "g_0" in cmd or "f_0" in cmd:
1486
- shareUpdateWithChannel(update=update, context=context)
1545
+ shareUpdateWithChannel(update=update, context=context,optionChoices=msg)
1487
1546
  shouldScan = False
1488
1547
  if len(args) > 0:
1489
1548
  shouldScan = True
@@ -1504,17 +1563,19 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1504
1563
  )
1505
1564
  if result:
1506
1565
  sendRequestSubmitted(cmd.upper(), update=update, context=context)
1566
+ scanRequest = cmd.upper().replace(" ", "").replace(">", "_").replace(":","_").replace("_D","").upper()
1567
+ sendSubscriptionOption(update,context,scanRequest)
1507
1568
  return START_ROUTES
1508
1569
  else:
1509
1570
  if cmd in ["x"]:
1510
1571
  cmdText = "For option 0 <Screen stocks by the stock name>, please type in the command in the following format\n/X_0 SBIN or /X_0_0 SBIN and hit send where SBIN is the NSE stock code.For multiple stocks, you can type in /X_0 SBIN,ICICIBANK,OtherStocks . You can put in any number of stocks separated by space or comma(,)."
1511
1572
  """Send a message when the command /help is issued."""
1512
- cmdText = f"{cmdText}\n\nClick /start if you want to restart the session."
1513
- update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"))
1573
+ cmdText = f"{cmdText}\nClick /start if you want to restart the session."
1574
+ update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"),reply_markup=default_markup(user=user),parse_mode="HTML")
1514
1575
  return START_ROUTES
1515
1576
 
1516
1577
  if "p_" in cmd:
1517
- shareUpdateWithChannel(update=update, context=context)
1578
+ shareUpdateWithChannel(update=update, context=context,optionChoices=msg)
1518
1579
  selection = cmd.split("_")
1519
1580
  if len(selection) == 2:
1520
1581
  m0.renderForMenu(
@@ -1541,8 +1602,8 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1541
1602
  cmdText = (
1542
1603
  f"{cmdText}\n\n{cmd.commandTextKey()} for {cmd.commandTextLabel()}"
1543
1604
  )
1544
- cmdText = f"{cmdText}\n\nClick /start if you want to restart the session."
1545
- update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"))
1605
+ cmdText = f"{cmdText}\nClick /start if you want to restart the session."
1606
+ update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"),reply_markup=default_markup(user=user),parse_mode="HTML")
1546
1607
  return START_ROUTES
1547
1608
  elif len(selection) == 3:
1548
1609
  m0.renderForMenu(
@@ -1556,8 +1617,8 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1556
1617
  cmdText = (
1557
1618
  f"{cmdText}\n\n/{cmd.upper()}{indexCmd.commandTextKey().replace('/X','')} for Piped scan of {indexCmd.commandTextLabel().replace('Scanners >',cmd.upper()+' >')}"
1558
1619
  )
1559
- cmdText = f"{cmdText}\n\nClick /start if you want to restart the session."
1560
- update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"))
1620
+ cmdText = f"{cmdText}\nClick /start if you want to restart the session."
1621
+ update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"),reply_markup=default_markup(user=user),parse_mode="HTML")
1561
1622
  return START_ROUTES
1562
1623
  elif len(selection) == 4:
1563
1624
  options = ":".join(selection)
@@ -1570,10 +1631,12 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1570
1631
  )
1571
1632
  if result:
1572
1633
  sendRequestSubmitted(cmd.upper(), update=update, context=context)
1634
+ scanRequest = cmd.upper().replace(" ", "").replace(">", "_").replace(":","_").replace("_D","").upper()
1635
+ sendSubscriptionOption(update,context,scanRequest)
1573
1636
  return START_ROUTES
1574
1637
 
1575
1638
  if "x_" in cmd or "b_" in cmd or "g_" in cmd:
1576
- shareUpdateWithChannel(update=update, context=context)
1639
+ shareUpdateWithChannel(update=update, context=context,optionChoices=msg)
1577
1640
  selection = cmd.split("_")
1578
1641
  if len(selection) == 2:
1579
1642
  m0.renderForMenu(
@@ -1605,14 +1668,16 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1605
1668
  )
1606
1669
  if result:
1607
1670
  sendRequestSubmitted(cmd.upper(), update=update, context=context)
1671
+ scanRequest = cmd.upper().replace(" ", "").replace(">", "_").replace(":","_").replace("_D","").upper()
1672
+ sendSubscriptionOption(update,context,scanRequest)
1608
1673
  return START_ROUTES
1609
1674
  elif (
1610
1675
  "x_" in cmd and selectedMenu.menuKey == "0"
1611
1676
  ): # a specific stock by name
1612
1677
  cmdText = "For option 0 <Screen stocks by the stock name>, please type in the command in the following format\n/X_0 SBIN or /X_0_0 SBIN and hit send where SBIN is the NSE stock code.For multiple stocks, you can type in /X_0 SBIN,ICICIBANK,OtherStocks. You can put in any number of stocks separated by space or comma(,)."
1613
1678
  """Send a message when the command /help is issued."""
1614
- cmdText = f"{cmdText}\n\nClick /start if you want to restart the session."
1615
- update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"))
1679
+ cmdText = f"{cmdText}\nClick /start if you want to restart the session."
1680
+ update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"),reply_markup=default_markup(user=user),parse_mode="HTML")
1616
1681
  return START_ROUTES
1617
1682
  cmds = m2.renderForMenu(
1618
1683
  selectedMenu=selectedMenu,
@@ -1624,8 +1689,8 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1624
1689
  cmdText = (
1625
1690
  f"{cmdText}\n\n{cmd.commandTextKey()} for {cmd.commandTextLabel()}"
1626
1691
  )
1627
- cmdText = f"{cmdText}\n\nClick /start if you want to restart the session."
1628
- update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"))
1692
+ cmdText = f"{cmdText}\nClick /start if you want to restart the session."
1693
+ update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"),reply_markup=default_markup(user=user),parse_mode="HTML")
1629
1694
  return START_ROUTES
1630
1695
  elif len(selection) == 3:
1631
1696
  m0.renderForMenu(
@@ -1662,8 +1727,8 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1662
1727
  )
1663
1728
  for cmd in cmds:
1664
1729
  cmdText = f"{cmdText}\n\n{cmd.commandTextKey()} for {cmd.commandTextLabel()}"
1665
- cmdText = f"{cmdText}\n\nClick /start if you want to restart the session."
1666
- update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"))
1730
+ cmdText = f"{cmdText}\nClick /start if you want to restart the session."
1731
+ update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"),reply_markup=default_markup(user=user),parse_mode="HTML")
1667
1732
  return START_ROUTES
1668
1733
  else:
1669
1734
  if selection[2] == "4": # Last N days
@@ -1720,8 +1785,8 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1720
1785
  )
1721
1786
  for cmd in cmds:
1722
1787
  cmdText = f"{cmdText}\n\n{cmd.commandTextKey()} for {cmd.commandTextLabel()}"
1723
- cmdText = f"{cmdText}\n\nClick /start if you want to restart the session."
1724
- update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"))
1788
+ cmdText = f"{cmdText}\nClick /start if you want to restart the session."
1789
+ update.message.reply_text(sanitiseTexts(f"Choose an option:\n{cmdText}"),reply_markup=default_markup(user=user),parse_mode="HTML")
1725
1790
  return START_ROUTES
1726
1791
 
1727
1792
  options = ":".join(selection)
@@ -1734,31 +1799,39 @@ def command_handler(update: Update, context: CallbackContext) -> None:
1734
1799
  )
1735
1800
  if result:
1736
1801
  sendRequestSubmitted(cmd.upper(), update=update, context=context)
1802
+ scanRequest = cmd.upper().replace(" ", "").replace(">", "_").replace(":","_").replace("_D","").upper()
1803
+ sendSubscriptionOption(update,context,scanRequest)
1737
1804
  return START_ROUTES
1738
1805
  if cmd == "y" or cmd == "h":
1739
- shareUpdateWithChannel(update=update, context=context)
1806
+ shareUpdateWithChannel(update=update, context=context,optionChoices=msg)
1740
1807
  from pkscreener.globals import showSendConfigInfo, showSendHelpInfo
1741
1808
  if cmd == "y":
1742
1809
  showSendConfigInfo(defaultAnswer='Y',user=str(update.message.from_user.id))
1743
1810
  elif cmd == "h":
1744
1811
  showSendHelpInfo(defaultAnswer='Y',user=str(update.message.from_user.id))
1745
- # result = launchScreener(
1746
- # options=f"{cmd.upper()}:",
1747
- # user=update.message.from_user,
1748
- # context=context,
1749
- # optionChoices=cmd.upper(),
1750
- # update=update,
1751
- # )
1752
- # sendRequestSubmitted(cmd.upper(), update=update, context=context)
1753
1812
  return START_ROUTES
1754
- update.message.reply_text(sanitiseTexts(f"{cmd.upper()} : Not implemented yet!"))
1813
+ update.message.reply_text(sanitiseTexts(f"{cmd.upper()} : Not implemented yet!"),reply_markup=default_markup(user=user),parse_mode="HTML")
1755
1814
  help_command(update=update, context=context)
1756
1815
 
1757
1816
 
1758
1817
  def sendRequestSubmitted(optionChoices, update, context):
1759
- menuText = f"Thank you for choosing {optionChoices}. You will receive the notification/results in about 1-2 minutes! \n\nConsider donating to help keep this project going:\nUPI: PKScreener@APL \nor\nhttps://github.com/sponsors/pkjmesra?frequency=recurring&sponsor=pkjmesra"
1760
- update.message.reply_text(sanitiseTexts(menuText))
1761
- help_command(update=update, context=context)
1818
+ updateCarrier = None
1819
+ if update is None:
1820
+ return
1821
+ else:
1822
+ if hasattr(update, "callback_query") and update.callback_query is not None:
1823
+ updateCarrier = update.callback_query
1824
+ if hasattr(update, "message") and update.message is not None:
1825
+ updateCarrier = update.message
1826
+ if hasattr(update, "effective_message") and update.effective_message is not None:
1827
+ updateCarrier = update.effective_message
1828
+ if updateCarrier is None:
1829
+ return
1830
+ # Get user that sent /start and log his name
1831
+ user = updateCarrier.from_user
1832
+ menuText = f"Thank you for choosing {optionChoices}. You will receive the notification/results in about 1-2 minutes! \n\nConsider donating to help keep this project going:\nUPI: <a href='https://tinyurl.com/v7h3t233'>PKScreener@APL</a> \nor <a href='https://github.com/sponsors/pkjmesra?frequency=recurring&sponsor=pkjmesra'>sponsor</a>"
1833
+ update.message.reply_text(sanitiseTexts(menuText),reply_markup=default_markup(user=user),parse_mode="HTML")
1834
+ # help_command(update=update, context=context)
1762
1835
  shareUpdateWithChannel(
1763
1836
  update=update, context=context, optionChoices=optionChoices
1764
1837
  )
@@ -1799,15 +1872,16 @@ def help_command(update: Update, context: CallbackContext) -> None:
1799
1872
  asList=True,
1800
1873
  renderStyle=MenuRenderStyle.STANDALONE,
1801
1874
  )
1802
- cmdText = "\n/otp to generate an OTP to login to PKScreener desktop console\n\n/check UPI_UTR_HERE_After_Making_Payment to share transaction reference number to automatically enable subscription after making payment via UPI"
1875
+ cmdText = "/otp to generate an OTP to login to PKScreener desktop console\n/check UPI_UTR_HERE_After_Making_Payment to share transaction reference number to automatically enable subscription after making payment via UPI"
1803
1876
  for cmd in cmds:
1804
- cmdText = f"{cmdText}\n\n{cmd.commandTextKey()} for {cmd.commandTextLabel()}"
1805
- reply_markup = default_markup([])
1877
+ if cmd.menuKey not in TOP_LEVEL_MARKUP_SKIP_MENUS:
1878
+ cmdText = f"{cmdText}\n{cmd.commandTextKey()} : {cmd.commandTextLabel()}"
1806
1879
 
1807
1880
  """Send a message when the command /help is issued."""
1808
1881
  if update is not None and update.message is not None:
1809
1882
  update.message.reply_text(
1810
- sanitiseTexts(f"You can begin by typing in /start (Recommended) and hit send!\n\nOR\n\nChoose an option:\n{cmdText}\n\nWe recommend you start by clicking on this /start")
1883
+ sanitiseTexts(f"You can begin by typing in /start (Recommended) and hit send!\nOR\nChoose an option:\n{cmdText}\n\nWe recommend you start by clicking on this /start"),
1884
+ reply_markup=default_markup(user=user),parse_mode="HTML"
1811
1885
  ) # \n\nThis bot restarts every hour starting at 5:30am IST until 10:30pm IST to keep it running on free servers. If it does not respond, please try again in a minutes to avoid the restart duration!
1812
1886
  query = update.message
1813
1887
  message = f"Name: <b>{query.from_user.first_name}</b>, Username:@{query.from_user.username} with ID: <b>@{str(query.from_user.id)}</b> began using the bot!"
@@ -1815,8 +1889,7 @@ def help_command(update: Update, context: CallbackContext) -> None:
1815
1889
  context.bot.send_message(
1816
1890
  chat_id=int(f"-{Channel_Id}"), text=message, parse_mode="HTML"
1817
1891
  )
1818
- DBManager().getOTP(user.id,user.username,f"{user.first_name} {user.last_name}",validityIntervalInSeconds=configManager.otpInterval)
1819
-
1892
+ registerUser(user)
1820
1893
 
1821
1894
  def _shouldAvoidResponse(update):
1822
1895
  sentFrom = []
@@ -2010,6 +2083,7 @@ def runpkscreenerbot(availability=True) -> None:
2010
2083
  CallbackQueryHandler(Level2, pattern="^" + str("CB_")),
2011
2084
  CallbackQueryHandler(Level2, pattern="^" + str("CP_")),
2012
2085
  CallbackQueryHandler(subscribeToScannerAlerts, pattern="^" + str("SUB_")),
2086
+ CallbackQueryHandler(viewSubscriptionOptions, pattern="^" + str("VS_")),
2013
2087
  # CallbackQueryHandler(Level2, pattern="^" + str("CG_")),
2014
2088
  CallbackQueryHandler(end, pattern="^" + str("CZ") + "$"),
2015
2089
  CallbackQueryHandler(start, pattern="^"),
@@ -2038,6 +2112,7 @@ def runpkscreenerbot(availability=True) -> None:
2038
2112
  if bot_available:
2039
2113
  # Run the intraday monitor
2040
2114
  initializeIntradayTimer()
2115
+ loadRegisteredUsers()
2041
2116
  # Run the bot until the user presses Ctrl-C
2042
2117
  # application.run_polling(allowed_updates=Update.ALL_TYPES)
2043
2118
  # Start the Bot