Skip to content

Implement Telegram bot for product sales and balance - #60

Open
unnjjj300-arch wants to merge 1 commit into
aeither:mainfrom
unnjjj300-arch:patch-3
Open

unnjjj300-arch wants to merge 1 commit into
aeither:mainfrom
unnjjj300-arch:patch-3

Conversation

@unnjjj300-arch

@unnjjj300-arch unnjjj300-arch commented Jun 17, 2026 •

Copy link
Copy Markdown

Added a Telegram bot implementation with user balance management and product purchasing functionality.

Summary by CodeRabbit

  • New Features
    • Telegram bot with inline product catalog and purchase functionality
    • User balance tracking and persistent data storage
    • Admin review and approval workflow for orders
    • Screenshot-based verification system for balance top-ups
    • OTP and number submission handling for transactions

Added a Telegram bot implementation with user balance management and product purchasing functionality.
@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A railway.toml file is added containing a complete Python Telegram bot implementation and a Flask keep-alive server. The bot manages user balances persisted in balances.json, handles product purchases via inline keyboards, routes purchases through admin approval steps involving phone number and OTP relay, and starts Flask in a background thread alongside bot.infinity_polling().

Changes

Telegram Bot Purchase & Admin Approval Flow

Layer / File(s) Summary
Bot setup, balance persistence, and global state
railway.toml
Imports, bot initialization with hardcoded API token and admin chat ID, load_balances/save_balances over balances.json, global user_balances and current_deals dicts, product list, and /start//help handler with reply keyboard.
User keyboard, product selection, and screenshot upload
railway.toml
Reply-keyboard message handler (buy, balance, payment, support), callback_select_product (balance check, pending deal, admin notification), and handle_screenshot (forwards photo to admin with add/reject inline buttons).
Admin approval callbacks and deal completion
railway.toml
handle_all_callbacks branches on callback prefixes to reject or credit balance, route admin phone-number input via register_next_step_handler, relay OTP, and on finish deduct product price and clear current_deals. process_admin_number and process_admin_otp send phone and OTP back to the user with inline controls.
Flask keep-alive server and bot startup
railway.toml
Flask app with / route, daemon thread launching Flask on a configurable port, and bot.infinity_polling() in the __main__ block.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hoppity-hop, a bot takes flight,
Keyboards and callbacks left and right,
Balances stored in a JSON nest,
Admin approves — the bunny's impressed!
Flask keeps watch while polling goes on,
A Telegram shop from dusk until dawn. 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement Telegram bot for product sales and balance' accurately describes the main change in the pull request—adding a new Telegram bot with product purchasing and balance management features.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Warning

⚠️ This pull request might be slop. It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@railway.toml`:
- Line 36: The PAYMENT_METHOD variable on line 36 contains sensitive personal
and financial account information that should not be stored in source code.
Remove the hardcoded payment details from this line and also check lines 70-71
which duplicate this information. Instead, store the payment account details in
deployment configuration (such as environment variables or secrets management)
and reference the configuration variable in the railway.toml file. This ensures
sensitive PII is not exposed in the repository and maintains a single source of
truth for payment instructions to prevent drift.
- Around line 164-169: The issue is that process_admin_otp sends the OTP to the
user before deducting their balance, creating a window where the user could
receive the product without payment if the admin's manual "Cut Balance & Finish"
action is missed or fails. Move the balance deduction or reservation logic to
execute before sending the OTP message (before the bot.send_message call with
the OTP text), ensuring the user's funds are atomically debited or reserved
before they receive the OTP and can access the product.
- Around line 100-102: The balance top-up callbacks (add_150_, add_250_,
rej_bal_) currently credit the user's balance every time the callback is
processed, making them non-idempotent and vulnerable to duplicate credits if
clicked multiple times. Add a unique request/top-up ID to the callback_data
along with the user_id (for example, include a request_id parameter in the
callback_data string), then in the callback handlers for these add_* and
rej_bal_ operations, query a persisted request tracking table before mutating
user_balances to verify the request status is still pending, and atomically
update that request's status to approved or rejected immediately after
processing to prevent reprocessing, rather than relying only on message caption
edits.
- Around line 171-177: The code imports the Flask module, but Flask is not
listed as a dependency in the project's pyproject.toml file, which will cause an
import failure at runtime. Add flask as a project dependency in the dependencies
section of pyproject.toml to ensure it is available when the Flask import
statement is executed.
- Around line 14-25: The load_balances function uses a bare except clause that
silently returns an empty dictionary when the balance file is corrupted or
unreadable, which can lead to permanent loss of user balances if save_balances
is called afterward. Replace the bare except with specific exception handling
that logs the actual error and re-raises it to fail loudly rather than silently
resetting the ledger. Additionally, modify save_balances to use atomic file
writing (write to a temporary file first, then rename it) to ensure that a
partial write does not corrupt the user funds data in the actual balance file.
- Around line 108-146: The handle_all_callbacks function processes sensitive
operations like balance additions, rejections, and OTP sending without verifying
that the user performing the action is the admin. Add an authorization check
comparing call.from_user.id against ADMIN_CHAT_ID at the beginning of the
function or before processing any admin-level actions (the "add", "rej",
"admin_*", and "user_reqotp" cases). If the user is not authorized, reject the
callback request with an appropriate error message instead of proceeding with
the operation.
- Around line 8-10: The Telegram bot token and admin chat ID are hardcoded
directly in the source code, which is a security risk. Remove the hardcoded
string values for API_TOKEN and ADMIN_CHAT_ID, and instead load both from
environment variables using appropriate environment variable retrieval methods
(such as os.getenv or similar). Ensure the bot initialization with
telebot.TeleBot references the token retrieved from the environment variable
rather than a hardcoded string.
- Around line 82-90: The code overwrites existing pending orders in
current_deals[user_id] when a user selects multiple products, causing admin
buttons to operate on the wrong product. Add a check before creating a new entry
in current_deals to prevent overwriting if a deal is already pending for that
user_id, or implement a unique deal_id system by replacing user_id with a unique
identifier in the callback_data parameter of the admin_sendnum callback and
storing deals with that unique deal_id as the key instead of just user_id.
Ensure the admin button callbacks reference the correct product and price by
maintaining proper deal tracking throughout the transaction lifecycle.
- Around line 2-3: The railway.toml file contains Python code starting with the
import telebot statement on line 3, which breaks TOML parsing. Remove all Python
code (from the import telebot line through the end of the telebot
implementation) from railway.toml and move it into main.py or a separate Python
module. Keep railway.toml containing only the startCommand configuration that
references the entry point (main.py), allowing the deployment configuration file
to remain valid TOML.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ef02f006-e1cb-47a4-a289-dbe326d25866

📥 Commits

Reviewing files that changed from the base of the PR and between 5cfd740 and 66e5c3b.

📒 Files selected for processing (1)
  • railway.toml

Comment thread railway.toml
Comment on lines 2 to +3
startCommand = "uv run python main.py"
import telebot

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify that railway.toml is valid TOML.
# Expected result today: TOMLDecodeError near the first Python line.

python - <<'PY'
import pathlib
import tomllib

path = pathlib.Path("railway.toml")
tomllib.loads(path.read_text(encoding="utf-8"))
print("railway.toml parses successfully")
PY

Repository: aeither/telegram-bot-python

Length of output: 799


Move Python code out of railway.toml—the file cannot parse as TOML.

Line 3 begins Python code (import telebot) inside the TOML config, causing a parse error at line 3, column 8. Since startCommand on line 2 runs main.py (not the embedded bot code), move lines 3–183 into main.py or a separate Python module and keep railway.toml as deployment config only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@railway.toml` around lines 2 - 3, The railway.toml file contains Python code
starting with the import telebot statement on line 3, which breaks TOML parsing.
Remove all Python code (from the import telebot line through the end of the
telebot implementation) from railway.toml and move it into main.py or a separate
Python module. Keep railway.toml containing only the startCommand configuration
that references the entry point (main.py), allowing the deployment configuration
file to remain valid TOML.

Comment thread railway.toml
Comment on lines +8 to +10
API_TOKEN = '8960267703:AAFdc1t16LXQibSMS82-qy4jAzwnPQuYatM'
bot = telebot.TeleBot(API_TOKEN)
ADMIN_CHAT_ID = 7457612833

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Revoke the committed bot token and load secrets from the environment.

The Telegram bot token is hardcoded in source. Treat it as compromised, rotate it with BotFather, and use environment variables for both the token and deployment-specific admin ID.

🔐 Proposed fix
-API_TOKEN = '<hardcoded bot token>'
-bot = telebot.TeleBot(API_TOKEN)
-ADMIN_CHAT_ID = 7457612833
+API_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
+bot = telebot.TeleBot(API_TOKEN)
+ADMIN_CHAT_ID = int(os.environ["ADMIN_CHAT_ID"])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@railway.toml` around lines 8 - 10, The Telegram bot token and admin chat ID
are hardcoded directly in the source code, which is a security risk. Remove the
hardcoded string values for API_TOKEN and ADMIN_CHAT_ID, and instead load both
from environment variables using appropriate environment variable retrieval
methods (such as os.getenv or similar). Ensure the bot initialization with
telebot.TeleBot references the token retrieved from the environment variable
rather than a hardcoded string.

Comment thread railway.toml
Comment on lines +14 to +25
def load_balances():
if os.path.exists(BALANCES_FILE):
try:
with open(BALANCES_FILE, "r") as f:
return {int(k): v for k, v in json.load(f).items()}
except:
return {}
return {}

def save_balances():
with open(BALANCES_FILE, "w") as f:
json.dump(user_balances, f)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not silently reset the balance ledger on load errors.

The bare except returns an empty ledger for corrupted/unreadable JSON; the next save_balances() can permanently overwrite real balances. Fail loudly and write atomically so a partial write does not corrupt user funds.

💾 Proposed fix
 def load_balances():
     if os.path.exists(BALANCES_FILE):
         try:
-            with open(BALANCES_FILE, "r") as f:
+            with open(BALANCES_FILE, "r", encoding="utf-8") as f:
                 return {int(k): v for k, v in json.load(f).items()}
-        except:
-            return {}
+        except (OSError, ValueError, json.JSONDecodeError) as exc:
+            raise RuntimeError(f"Unable to load {BALANCES_FILE}") from exc
     return {}
 
 def save_balances():
-    with open(BALANCES_FILE, "w") as f:
-        json.dump(user_balances, f)
+    tmp_file = f"{BALANCES_FILE}.tmp"
+    with open(tmp_file, "w", encoding="utf-8") as f:
+        json.dump(user_balances, f)
+    os.replace(tmp_file, BALANCES_FILE)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@railway.toml` around lines 14 - 25, The load_balances function uses a bare
except clause that silently returns an empty dictionary when the balance file is
corrupted or unreadable, which can lead to permanent loss of user balances if
save_balances is called afterward. Replace the bare except with specific
exception handling that logs the actual error and re-raises it to fail loudly
rather than silently resetting the ledger. Additionally, modify save_balances to
use atomic file writing (write to a temporary file first, then rename it) to
ensure that a partial write does not corrupt the user funds data in the actual
balance file.

Comment thread railway.toml
"3": {"name": "Pakistan number", "price": 150}
}

PAYMENT_METHOD = "Easypaisa: 03293891772: Muhammad zubair"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move payment account details out of source.

The payment method contains personal/financial account details and Line 71 duplicates part of it. Store this in deployment configuration and render it from one source to avoid leaking PII and drifting payment instructions.

🔐 Proposed fix
-PAYMENT_METHOD = "..."
+PAYMENT_METHOD = os.environ["PAYMENT_METHOD"]
-        bot.send_message(message.chat.id, f"💰 **Payment Details:**\n\n{PAYMENT_METHOD}\n\n📌 Account Number: 03293891772\n👉 Screenshot send karein.")
+        bot.send_message(message.chat.id, f"💰 **Payment Details:**\n\n{PAYMENT_METHOD}\n\n👉 Screenshot send karein.")

Also applies to: 70-71

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@railway.toml` at line 36, The PAYMENT_METHOD variable on line 36 contains
sensitive personal and financial account information that should not be stored
in source code. Remove the hardcoded payment details from this line and also
check lines 70-71 which duplicate this information. Instead, store the payment
account details in deployment configuration (such as environment variables or
secrets management) and reference the configuration variable in the railway.toml
file. This ensures sensitive PII is not exposed in the repository and maintains
a single source of truth for payment instructions to prevent drift.

Comment thread railway.toml
Comment on lines +82 to +90
if product:
if user_balances.get(user_id, 0) >= product['price']:
current_deals[user_id] = {"product_name": product['name'], "price": product['price']}

markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("📤 Send Number", callback_data=f"admin_sendnum_{user_id}"))

admin_text = f"🔔 **Naya Number Order!**\n👤 User ID: `{user_id}`\n📦 Product: {product['name']}\n💵 Keemat: {product['price']} PKR\n\n👇 Niche button daba kar number dein:"
bot.send_message(ADMIN_CHAT_ID, admin_text, reply_markup=markup, parse_mode="Markdown")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent pending orders from being overwritten.

current_deals[user_id] supports only one pending deal. If the user selects another product before admin finishes the first one, the old admin buttons still target the same user but now operate on the new product/price. Block new orders while one is pending, or use a unique deal_id in every callback.

🧾 Minimal guard
         if user_balances.get(user_id, 0) >= product['price']:
+            if user_id in current_deals:
+                bot.answer_callback_query(call.id, "You already have a pending order.", show_alert=True)
+                return
             current_deals[user_id] = {"product_name": product['name'], "price": product['price']}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@railway.toml` around lines 82 - 90, The code overwrites existing pending
orders in current_deals[user_id] when a user selects multiple products, causing
admin buttons to operate on the wrong product. Add a check before creating a new
entry in current_deals to prevent overwriting if a deal is already pending for
that user_id, or implement a unique deal_id system by replacing user_id with a
unique identifier in the callback_data parameter of the admin_sendnum callback
and storing deals with that unique deal_id as the key instead of just user_id.
Ensure the admin button callbacks reference the correct product and price by
maintaining proper deal tracking throughout the transaction lifecycle.

Comment thread railway.toml
Comment on lines +100 to +102
markup.add(types.InlineKeyboardButton("✅ Add 150 PKR", callback_data=f"add_150_{user_id}"),
types.InlineKeyboardButton("✅ Add 250 PKR", callback_data=f"add_250_{user_id}"),
types.InlineKeyboardButton("❌ Reject", callback_data=f"rej_bal_{user_id}"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Make balance top-ups idempotent.

The add_* callback credits balance every time it is processed. Use a unique top-up/request ID with a persisted processed status before mutating user_balances; editing the caption is not an accounting guard.

🧾 Minimum direction
-    markup.add(types.InlineKeyboardButton("✅ Add 150 PKR", callback_data=f"add_150_{user_id}"),
-               types.InlineKeyboardButton("✅ Add 250 PKR", callback_data=f"add_250_{user_id}"),
-               types.InlineKeyboardButton("❌ Reject", callback_data=f"rej_bal_{user_id}"))
+    topup_id = create_persisted_topup_request(user_id, message.photo[-1].file_id)
+    markup.add(types.InlineKeyboardButton("✅ Add 150 PKR", callback_data=f"add_150_{topup_id}"),
+               types.InlineKeyboardButton("✅ Add 250 PKR", callback_data=f"add_250_{topup_id}"),
+               types.InlineKeyboardButton("❌ Reject", callback_data=f"rej_bal_{topup_id}"))

Then process only top-ups whose persisted status is still pending, and atomically mark them approved/rejected before acknowledging the callback.

Also applies to: 113-119, 121-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@railway.toml` around lines 100 - 102, The balance top-up callbacks (add_150_,
add_250_, rej_bal_) currently credit the user's balance every time the callback
is processed, making them non-idempotent and vulnerable to duplicate credits if
clicked multiple times. Add a unique request/top-up ID to the callback_data
along with the user_id (for example, include a request_id parameter in the
callback_data string), then in the callback handlers for these add_* and
rej_bal_ operations, query a persisted request tracking table before mutating
user_balances to verify the request status is still pending, and atomically
update that request's status to approved or rejected immediately after
processing to prevent reprocessing, rather than relying only on message caption
edits.

Comment thread railway.toml
Comment on lines +108 to +146
@bot.callback_query_handler(func=lambda call: True)
def handle_all_callbacks(call):
data = call.data.split('_')
action = data[0]

if action == "add":
amount, target_user = int(data[1]), int(data[2])
if target_user not in user_balances: user_balances[target_user] = 0
user_balances[target_user] += amount
save_balances()
bot.send_message(target_user, f"🎉 Balance Loaded! Added {amount} PKR. Total: {user_balances[target_user]} PKR.")
bot.edit_message_caption(f"✅ Added {amount} PKR to {target_user}", chat_id=call.message.chat.id, message_id=call.message.message_id)

elif action == "rej" and data[1] == "bal":
target_user = int(data[2])
bot.send_message(target_user, "❌ Aapka screenshot reject kar diya gaya hai.")
bot.edit_message_caption("❌ Screenshot Rejected", chat_id=call.message.chat.id, message_id=call.message.message_id)

elif action == "admin" and data[1] == "sendnum":
target_user = data[2]
msg = bot.send_message(ADMIN_CHAT_ID, f"✍️ Is user `{target_user}` ke liye Number likh kar send karein:")
bot.register_next_step_handler(msg, process_admin_number, target_user)
bot.delete_message(chat_id=call.message.chat.id, message_id=call.message.message_id)

elif action == "user" and data[1] == "reqotp":
target_user = data[2]
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("🔑 Send OTP", callback_data=f"admin_sendotp_{target_user}"))
bot.send_message(ADMIN_CHAT_ID, f"📩 **ALERT!** User `{target_user}` OTP maang raha hai!", reply_markup=markup)
bot.edit_message_text("⏳ OTP Request Sent to Admin. Waiting...", chat_id=call.message.chat.id, message_id=call.message.message_id)

elif action == "admin" and data[1] == "sendotp":
target_user = data[2]
msg = bot.send_message(ADMIN_CHAT_ID, f"✍️ User `{target_user}` ke liye OTP Code likh kar send karein:")
bot.register_next_step_handler(msg, process_admin_otp, target_user)
bot.delete_message(chat_id=call.message.chat.id, message_id=call.message.message_id)

elif action == "admin" and data[1] == "finish":
target_user = int(data[2])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce admin authorization inside the callback handler.

The handler processes add, rej, and admin_* actions without checking who pressed the button. Do not rely only on chat routing; reject admin actions unless call.from_user.id == ADMIN_CHAT_ID.

🛡️ Proposed guard
 def handle_all_callbacks(call):
     data = call.data.split('_')
     action = data[0]
+
+    if action in {"add", "rej", "admin"} and call.from_user.id != ADMIN_CHAT_ID:
+        bot.answer_callback_query(call.id, "Unauthorized", show_alert=True)
+        return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@bot.callback_query_handler(func=lambda call: True)
def handle_all_callbacks(call):
data = call.data.split('_')
action = data[0]
if action == "add":
amount, target_user = int(data[1]), int(data[2])
if target_user not in user_balances: user_balances[target_user] = 0
user_balances[target_user] += amount
save_balances()
bot.send_message(target_user, f"🎉 Balance Loaded! Added {amount} PKR. Total: {user_balances[target_user]} PKR.")
bot.edit_message_caption(f"✅ Added {amount} PKR to {target_user}", chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "rej" and data[1] == "bal":
target_user = int(data[2])
bot.send_message(target_user, "❌ Aapka screenshot reject kar diya gaya hai.")
bot.edit_message_caption("❌ Screenshot Rejected", chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "admin" and data[1] == "sendnum":
target_user = data[2]
msg = bot.send_message(ADMIN_CHAT_ID, f"✍️ Is user `{target_user}` ke liye Number likh kar send karein:")
bot.register_next_step_handler(msg, process_admin_number, target_user)
bot.delete_message(chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "user" and data[1] == "reqotp":
target_user = data[2]
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("🔑 Send OTP", callback_data=f"admin_sendotp_{target_user}"))
bot.send_message(ADMIN_CHAT_ID, f"📩 **ALERT!** User `{target_user}` OTP maang raha hai!", reply_markup=markup)
bot.edit_message_text("⏳ OTP Request Sent to Admin. Waiting...", chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "admin" and data[1] == "sendotp":
target_user = data[2]
msg = bot.send_message(ADMIN_CHAT_ID, f"✍️ User `{target_user}` ke liye OTP Code likh kar send karein:")
bot.register_next_step_handler(msg, process_admin_otp, target_user)
bot.delete_message(chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "admin" and data[1] == "finish":
target_user = int(data[2])
`@bot.callback_query_handler`(func=lambda call: True)
def handle_all_callbacks(call):
data = call.data.split('_')
action = data[0]
if action in {"add", "rej", "admin"} and call.from_user.id != ADMIN_CHAT_ID:
bot.answer_callback_query(call.id, "Unauthorized", show_alert=True)
return
if action == "add":
amount, target_user = int(data[1]), int(data[2])
if target_user not in user_balances: user_balances[target_user] = 0
user_balances[target_user] += amount
save_balances()
bot.send_message(target_user, f"🎉 Balance Loaded! Added {amount} PKR. Total: {user_balances[target_user]} PKR.")
bot.edit_message_caption(f"✅ Added {amount} PKR to {target_user}", chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "rej" and data[1] == "bal":
target_user = int(data[2])
bot.send_message(target_user, "❌ Aapka screenshot reject kar diya gaya hai.")
bot.edit_message_caption("❌ Screenshot Rejected", chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "admin" and data[1] == "sendnum":
target_user = data[2]
msg = bot.send_message(ADMIN_CHAT_ID, f"✍️ Is user `{target_user}` ke liye Number likh kar send karein:")
bot.register_next_step_handler(msg, process_admin_number, target_user)
bot.delete_message(chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "user" and data[1] == "reqotp":
target_user = data[2]
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("🔑 Send OTP", callback_data=f"admin_sendotp_{target_user}"))
bot.send_message(ADMIN_CHAT_ID, f"📩 **ALERT!** User `{target_user}` OTP maang raha hai!", reply_markup=markup)
bot.edit_message_text("⏳ OTP Request Sent to Admin. Waiting...", chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "admin" and data[1] == "sendotp":
target_user = data[2]
msg = bot.send_message(ADMIN_CHAT_ID, f"✍️ User `{target_user}` ke liye OTP Code likh kar send karein:")
bot.register_next_step_handler(msg, process_admin_otp, target_user)
bot.delete_message(chat_id=call.message.chat.id, message_id=call.message.message_id)
elif action == "admin" and data[1] == "finish":
target_user = int(data[2])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@railway.toml` around lines 108 - 146, The handle_all_callbacks function
processes sensitive operations like balance additions, rejections, and OTP
sending without verifying that the user performing the action is the admin. Add
an authorization check comparing call.from_user.id against ADMIN_CHAT_ID at the
beginning of the function or before processing any admin-level actions (the
"add", "rej", "admin_*", and "user_reqotp" cases). If the user is not
authorized, reject the callback request with an appropriate error message
instead of proceeding with the operation.

Comment thread railway.toml
Comment on lines +164 to +169
def process_admin_otp(message, target_user):
otp_text = message.text
bot.send_message(target_user, f"🔑 **Aapka WhatsApp OTP Code:** `{otp_text}`")
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("💰 Cut Balance & Finish", callback_data=f"admin_finish_{target_user}"))
bot.send_message(ADMIN_CHAT_ID, f"📌 OTP bhej diya gaya hai. Balance Cut karein:", reply_markup=markup)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Debit or reserve balance before delivering the OTP.

process_admin_otp sends the OTP first, then relies on a later manual “Cut Balance & Finish” click. If that click is missed or fails, the user receives the product without being charged. Reserve funds when the order is created, or atomically debit before sending the OTP.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@railway.toml` around lines 164 - 169, The issue is that process_admin_otp
sends the OTP to the user before deducting their balance, creating a window
where the user could receive the product without payment if the admin's manual
"Cut Balance & Finish" action is missed or fails. Move the balance deduction or
reservation logic to execute before sending the OTP message (before the
bot.send_message call with the OTP text), ensuring the user's funds are
atomically debited or reserved before they receive the OTP and can access the
product.

Comment thread railway.toml
Comment on lines +171 to +177
from flask import Flask
app = Flask('')
@app.route('/')
def home(): return "Bot is Alive!"

import threading
def run_db(): app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify Flask is declared in project dependencies.
# Expected result today: assertion failure unless pyproject.toml has been updated elsewhere.

python - <<'PY'
import pathlib
import tomllib

data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
deps = data.get("project", {}).get("dependencies", [])
print("\n".join(deps))
assert any(dep.lower().startswith("flask") for dep in deps), "Flask dependency is not declared"
PY

Repository: aeither/telegram-bot-python

Length of output: 263


Add Flask to project dependencies in pyproject.toml.

The code imports Flask but it is not declared in the project dependencies. This will cause an import failure at runtime. Add flask as a dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@railway.toml` around lines 171 - 177, The code imports the Flask module, but
Flask is not listed as a dependency in the project's pyproject.toml file, which
will cause an import failure at runtime. Add flask as a project dependency in
the dependencies section of pyproject.toml to ensure it is available when the
Flask import statement is executed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant