Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ async def validate_channel(application):
FREE_COMMANDS = [
BotCommand("start", "Start StrideBot"),
BotCommand("help", "View all commands"),
BotCommand("commands", "View all commands by tier"),
BotCommand("price", "Live crypto price"),
BotCommand("top10", "Top 10 cryptos by market cap"),
BotCommand("marketupdate", "Full market overview"),
Expand Down Expand Up @@ -364,6 +365,7 @@ def main():

application.add_handler(CommandHandler("start", protected(handlers.start)))
application.add_handler(CommandHandler("help", protected(handlers.help_command)))
application.add_handler(CommandHandler("commands", protected(handlers.commands)))
application.add_handler(CommandHandler("clear", protected(handlers.clear)))

application.add_handler(CommandHandler("price", protected(handlers.price)))
Expand Down
1 change: 1 addition & 0 deletions bot/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from handlers.core import (
start,
help_command,
commands,
clear,
about,
handle_message,
Expand Down
67 changes: 67 additions & 0 deletions bot/handlers/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,9 +705,76 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
"/createcoupon <code> <plan> <uses> — Create coupon\n"
"/listcoupons — View all coupons\n"
)
base += "\n\n💡 Use /commands to see the full command list by tier"
await safe_reply(update, base)


async def commands(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""List all available commands based on user tier."""
user_id = update.effective_user.id
tier = ai.get_user_tier(user_id)

free_cmds = (
"<b>🆓 FREE TIER COMMANDS</b>\n\n"
"<b>Prices & Market</b>\n"
"/price <coin> — Live price (e.g. /price BTC)\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Escape argument placeholders before sending HTML

Because this response is sent through safe_reply_html(..., parse_mode="HTML"), placeholders like <coin> are parsed as HTML tags; Telegram rejects unsupported tags, and the fallback strips all <...> text, so /commands will show /price — Live price and similarly remove every argument name. Escape these placeholders as &lt;coin&gt; (or avoid HTML parse mode for command syntax) so users get the actual usage text.

Useful? React with 👍 / 👎.

"/top10 — Top 10 coins by market cap\n"
"/marketupdate — Full market overview\n"
"/rates — NGN exchange rates\n\n"
"<b>News</b>\n"
"/news — Latest crypto news (4 categories)\n\n"
"<b>Account</b>\n"
"/premium — Upgrade to premium\n"
"/myplan — Check your subscription\n"
"/redeem <code> — Redeem coupon code\n"
"/clear — Clear chat history\n"
"/about — About StrideBot\n"
"/timezone — Set your timezone"
)

premium_cmds = (
"\n\n<b>💎 PREMIUM COMMANDS</b>\n\n"
"<b>Analysis & Research</b>\n"
"/analysis <coin> — Deep AI analysis\n"
"/stock <symbol> — Stock price & data\n"
"/summary — AI news digest\n"
"/polymarket <topic> — Prediction market intelligence\n\n"
"<b>Alerts & Tracking</b>\n"
Comment on lines +738 to +742

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The /commands handler incorrectly lists free commands like /analysis and /alert as premium-only, misleading users about feature availability.
Severity: LOW

Suggested Fix

Remove the free commands (e.g., /analysis, /summary, /polymarket, /alert, /watchlist, /forex and related commands) from the premium_cmds string within the commands handler in bot/handlers/core.py. The displayed command list should accurately reflect the tiers defined in bot/bot.py.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: bot/handlers/core.py#L738-L742

Potential issue: The `/commands` handler incorrectly lists several commands as
premium-only when they are actually available to free-tier users. The `premium_cmds`
string in `bot/handlers/core.py` includes commands such as `/analysis`, `/summary`,
`/polymarket`, `/alert`, `/watchlist`, and `/forex`. However, these are all registered
in the `FREE_COMMANDS` list in `bot/bot.py` and do not have any tier-based restrictions
in their respective handlers. This will cause the bot to incorrectly inform free users
that they need to upgrade to use features they already have access to, creating a
confusing user experience.

Did we get this right? 👍 / 👎 to inform future reviews.

"/alert — Set price alert\n"
"/myalerts — View active alerts\n"
"/cancelalert — Remove alert\n"
"/watch <symbol> — Add to watchlist (20 max)\n"
"/unwatch <symbol> — Remove from watchlist\n"
"/watchlist — View watchlist with AI commentary\n\n"
"<b>Forex & Markets</b>\n"
"/forex <pair> — Live forex rate\n"
"/forexanalysis <pair> — AI forex analysis"
)

admin_cmds = (
"\n\n<b>👑 ADMIN COMMANDS</b>\n\n"
"/broadcast — Send message to all users\n"
"/testpost <name> — Trigger scheduled post\n"
"/stats — Bot statistics\n"
"/listusers — List all users\n"
"/addpremium <user_id> <days> — Grant premium\n"
"/removepremium <user_id> — Revoke premium\n"
"/createcoupon <code> <plan> <max_uses> — Create coupon\n"
"/listcoupons — View all coupons\n"
"/trending — Run trend intelligence pipeline\n"
"/approvetrend — Approve trend post for channel"
)

if tier == "admin":
text = free_cmds + premium_cmds + admin_cmds
elif tier in ["premium", "scheduler"]:
text = free_cmds + premium_cmds
else:
text = free_cmds + "\n\n💡 <i>Upgrade to premium for full access: /premium</i>"

log_event("commands_list")
await safe_reply_html(update, text)


async def clear(update: Update, context: ContextTypes.DEFAULT_TYPE):
await asyncio.to_thread(db.clear_history, update.effective_user.id)
Expand Down