Repository navigation
railway.toml - #44
railway.toml#44doreamon2343-cmyk wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughAdds a complete Telegram bot implementation with SQLite database integration, supporting user registration with referral tracking, balance management, daily bonuses, withdrawal requests with admin confirmation, and channel membership enforcement via callback handlers and decorators. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Bot as Telegram Bot
participant DB as SQLite Database
participant Admin as Admin Handler
User->>Bot: /start [referrer_id]
Bot->>DB: Check if user exists
alt User not in DB
Bot->>DB: Create new user entry
Bot->>User: Show "Join Channel" inline button
else User already exists
Bot->>User: Show main menu
end
User->>Bot: Click "Check" button
Bot->>DB: Verify channel membership
alt Not a member
Bot->>User: Prompt to join channel
else Is a member
Bot->>DB: Grant initial balance + referral bonus
Bot->>DB: Mark claim as completed (one-time)
Bot->>User: Confirm balance credited, show menu
end
User->>Bot: /withdraw
Bot->>User: Request QR code upload
User->>Bot: Send QR code
Bot->>Admin: Notify with withdrawal request + callback buttons
Admin->>Bot: Click approve/cancel
Bot->>DB: Update user balance accordingly
Bot->>User: Confirm withdrawal processed
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
railway.toml (1)
74-75: Bareexceptclauses hide errors.Multiple bare
except:clauses (lines 74, 112, 149) silently swallow all exceptions includingKeyboardInterrupt,SystemExit, and programming errors. This makes debugging difficult and can mask serious issues.♻️ Suggested improvement
- except: - return False + except Exception: + return FalseOr better, catch specific exceptions like
telebot.apihelper.ApiTelegramException.Also applies to: 112-112
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@railway.toml` around lines 74 - 75, Replace bare "except:" clauses with specific exception handling: catch concrete exceptions (e.g., telebot.apihelper.ApiTelegramException or Exception as e) instead of a bare except, re-raise system-level exceptions (KeyboardInterrupt, SystemExit) if caught, and log the caught exception (include e or its traceback) before returning False; locate the bare except usages in the functions containing the "except: return False" patterns and update them to "except telebot.apihelper.ApiTelegramException as e:" or "except Exception as e:" with logging and re-raise logic as appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@railway.toml`:
- Around line 211-229: The withdraw flow lacks per-user state validation: modify
handlers (get_qr, get_amount) to use a per-user state machine stored in
withdraw_requests keyed by uid with a state field (e.g., "awaiting_qr",
"awaiting_amount") and a timestamp; on photo receipt in get_qr verify the
sender's current state is "awaiting_qr" before saving qr and advancing to
"awaiting_amount", and in get_amount verify state is "awaiting_amount" before
parsing amount, checking get_balance(uid), sending to ADMIN_GROUP_ID and
clearing the entry; add a timeout/cleanup routine that removes stale
withdraw_requests entries after N minutes to prevent abandoned or interleaved
flows.
- Around line 9-13: The file contains hardcoded secrets (TOKEN, CHANNEL,
BOT_USERNAME, ADMIN_ID, ADMIN_GROUP_ID); replace these literal values with
environment lookups and remove them from the repo: stop using TOKEN and instead
read os.getenv('TELEGRAM_BOT_TOKEN') (as shown in main.py), and read CHANNEL,
BOT_USERNAME, ADMIN_ID, and ADMIN_GROUP_ID from environment variables (e.g.,
os.getenv('TELEGRAM_CHANNEL'), os.getenv('TELEGRAM_BOT_USERNAME'),
os.getenv('TELEGRAM_ADMIN_ID'), os.getenv('TELEGRAM_ADMIN_GROUP_ID')), update
any code that references these constants to use those env vars, revoke the
exposed token immediately and rotate it, and ensure the railway.toml no longer
contains secrets before committing.
- Around line 192-199: The task_check callback currently awards $0.02 on every
click; to prevent farming, add persistent claim tracking like the daily bonus:
add a task_claimed (boolean or timestamp) column to the users table, update the
check in task_check (function name task_check) to call check_join(uid) AND
verify task_claimed is false (or last_task_claim is older than allowed), only
call add_money(uid,0.02) and send success if not previously claimed, then set
task_claimed = true (or update last_task_claim) in the same transaction; update
any DB migration/ORM model and places that read/write user state to use
task_claimed/last_task_claim consistently.
- Around line 18-19: The current use of a single sqlite3 connection (db =
sqlite3.connect(...)) and cursor (c = db.cursor()) with check_same_thread=False
is unsafe for telebot’s multithreaded handlers; replace this pattern by creating
a fresh sqlite3 connection per request/handler (call sqlite3.connect(...) inside
the handler or a helper like get_db_connection()) or implement a connection pool
and ensure writes are serialized (e.g., via a dedicated DB worker or
threading.Lock around writes). Update all code that uses the global db or c to
obtain and close a local connection/cursor (or use context managers), and remove
reliance on the global db/db cursor to prevent concurrent-write corruption.
- Around line 1-256: railway.toml contains an entire Python bot implementation
(including TOKEN, bot = telebot.TeleBot(...), bot.infinity_polling(), etc.)
which must be removed so the file remains valid TOML; delete everything except
the TOML keys (leave startCommand = "uv run python main.py") and move any
intended code changes into main.py (do not duplicate sensitive values like TOKEN
in the TOML); ensure railway.toml only holds configuration and that main.py
contains the Python bot implementation and environment-variable usage.
---
Nitpick comments:
In `@railway.toml`:
- Around line 74-75: Replace bare "except:" clauses with specific exception
handling: catch concrete exceptions (e.g.,
telebot.apihelper.ApiTelegramException or Exception as e) instead of a bare
except, re-raise system-level exceptions (KeyboardInterrupt, SystemExit) if
caught, and log the caught exception (include e or its traceback) before
returning False; locate the bare except usages in the functions containing the
"except: return False" patterns and update them to "except
telebot.apihelper.ApiTelegramException as e:" or "except Exception as e:" with
logging and re-raise logic as appropriate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| [deploy] | ||
| startCommand = "uv run python main.py" | ||
| import telebot | ||
| from telebot import types | ||
| import sqlite3 | ||
| import time | ||
|
|
||
| # ===== CONFIG ===== | ||
| TOKEN = "7293546307:AAHKAYnZpKouA6JU9n7-IiEtMzbd_CqCOXI" | ||
| CHANNEL = "@virostore34" | ||
| BOT_USERNAME = "virostore_bot" | ||
| ADMIN_ID = 5099884611 | ||
| ADMIN_GROUP_ID = -1003732131155 | ||
|
|
||
| bot = telebot.TeleBot(TOKEN) | ||
|
|
||
| # ===== DATABASE ===== | ||
| db = sqlite3.connect("users.db", check_same_thread=False) | ||
| c = db.cursor() | ||
|
|
||
| c.execute(""" | ||
| CREATE TABLE IF NOT EXISTS users( | ||
| user_id INTEGER PRIMARY KEY, | ||
| balance REAL DEFAULT 0, | ||
| referrer INTEGER, | ||
| joined INTEGER DEFAULT 0, | ||
| banned INTEGER DEFAULT 0, | ||
| last_claim INTEGER DEFAULT 0 | ||
| ) | ||
| """) | ||
| db.commit() | ||
|
|
||
| # ===== DB FUNCTIONS ===== | ||
| def add_user(uid, ref=None): | ||
| c.execute("INSERT OR IGNORE INTO users(user_id,balance,referrer,joined,banned,last_claim) VALUES(?,?,?,?,?,?)", | ||
| (uid,0,ref,0,0,0)) | ||
| db.commit() | ||
|
|
||
| def get_balance(uid): | ||
| c.execute("SELECT balance FROM users WHERE user_id=?", (uid,)) | ||
| r = c.fetchone() | ||
| return r[0] if r else 0 | ||
|
|
||
| def add_money(uid, amt): | ||
| c.execute("UPDATE users SET balance=balance+? WHERE user_id=?", (amt,uid)) | ||
| db.commit() | ||
|
|
||
| def sub_money(uid, amt): | ||
| c.execute("UPDATE users SET balance=balance-? WHERE user_id=?", (amt,uid)) | ||
| db.commit() | ||
|
|
||
| def is_joined(uid): | ||
| c.execute("SELECT joined FROM users WHERE user_id=?", (uid,)) | ||
| r = c.fetchone() | ||
| return r[0] if r else 0 | ||
|
|
||
| def set_joined(uid): | ||
| c.execute("UPDATE users SET joined=1 WHERE user_id=?", (uid,)) | ||
| db.commit() | ||
|
|
||
| def is_banned(uid): | ||
| c.execute("SELECT banned FROM users WHERE user_id=?", (uid,)) | ||
| r = c.fetchone() | ||
| return r[0] if r else 0 | ||
|
|
||
| def is_admin(uid): | ||
| return uid == ADMIN_ID | ||
|
|
||
| # ===== CHECK JOIN ===== | ||
| def check_join(uid): | ||
| try: | ||
| member = bot.get_chat_member(CHANNEL, uid) | ||
| return member.status in ["member","administrator","creator"] | ||
| except: | ||
| return False | ||
|
|
||
| # ===== REQUIRE JOIN DECORATOR ===== | ||
| def require_join(func): | ||
| def wrapper(m): | ||
| uid = m.from_user.id | ||
| if is_banned(uid): | ||
| bot.send_message(m.chat.id,"🚫 អ្នកត្រូវបាន ban") | ||
| return | ||
| if not check_join(uid): | ||
| mk = types.InlineKeyboardMarkup() | ||
| mk.add(types.InlineKeyboardButton("📢 Join", url=f"https://t.me/{CHANNEL[1:]}")) | ||
| mk.add(types.InlineKeyboardButton("✅ Check", callback_data="check")) | ||
| bot.send_message(m.chat.id,"❌ Join channel មុន",reply_markup=mk) | ||
| return | ||
| return func(m) | ||
| return wrapper | ||
|
|
||
| # ===== MENU ===== | ||
| def menu(cid, uid): | ||
| mk = types.ReplyKeyboardMarkup(resize_keyboard=True) | ||
| mk.row("👤 គណនី","👥 Invite") | ||
| mk.row("🎁 Daily Bonus","📋 Tasks") | ||
| mk.row("💸 ដកប្រាក់") | ||
| if is_admin(uid): | ||
| mk.row("⚙️ Admin Panel") | ||
| bot.send_message(cid,"📋 VIP MENU",reply_markup=mk) | ||
|
|
||
| # ===== START ===== | ||
| @bot.message_handler(commands=['start']) | ||
| def start(m): | ||
| uid = m.from_user.id | ||
| args = m.text.split() | ||
| ref = None | ||
| if len(args) > 1: | ||
| try: | ||
| ref = int(args[1]) | ||
| except: ref=None | ||
| if ref == uid: ref=None | ||
|
|
||
| c.execute("SELECT user_id FROM users WHERE user_id=?", (uid,)) | ||
| if c.fetchone() is None: add_user(uid,ref) | ||
| else: add_user(uid) | ||
|
|
||
| if is_banned(uid): | ||
| bot.send_message(m.chat.id,"🚫 banned") | ||
| return | ||
|
|
||
| if not check_join(uid): | ||
| mk = types.InlineKeyboardMarkup() | ||
| mk.add(types.InlineKeyboardButton("📢 Join", url=f"https://t.me/{CHANNEL[1:]}")) | ||
| mk.add(types.InlineKeyboardButton("✅ Check", callback_data="check")) | ||
| bot.send_message(m.chat.id,"📢 Join channel first",reply_markup=mk) | ||
| return | ||
|
|
||
| bot.send_message(m.chat.id,"✅ Welcome! /start works now") | ||
| menu(m.chat.id, uid) | ||
|
|
||
| # ===== CALLBACK CHECK ===== | ||
| @bot.callback_query_handler(func=lambda c:c.data=="check") | ||
| def check_cb(ca): | ||
| uid = ca.from_user.id | ||
| if not check_join(uid): | ||
| bot.answer_callback_query(ca.id,"❌ Join first") | ||
| return | ||
|
|
||
| if is_joined(uid)==0: | ||
| add_money(uid,0.03) | ||
| set_joined(uid) | ||
| c.execute("SELECT referrer FROM users WHERE user_id=?", (uid,)) | ||
| ref=c.fetchone() | ||
| if ref and ref[0]: | ||
| add_money(ref[0],0.04) | ||
| try: bot.send_message(ref[0],"🎉 Referral +$0.04") | ||
| except: pass | ||
|
|
||
| bot.answer_callback_query(ca.id,"✅ Done") | ||
| menu(ca.message.chat.id,uid) | ||
|
|
||
| # ===== ACCOUNT ===== | ||
| @bot.message_handler(func=lambda m:m.text=="👤 គណនី") | ||
| @require_join | ||
| def acc(m): | ||
| bot.send_message(m.chat.id,f"💰 ${get_balance(m.from_user.id):.2f}") | ||
|
|
||
| # ===== INVITE ===== | ||
| @bot.message_handler(func=lambda m:m.text=="👥 Invite") | ||
| @require_join | ||
| def inv(m): | ||
| link=f"https://t.me/{BOT_USERNAME}?start={m.from_user.id}" | ||
| bot.send_message(m.chat.id,link) | ||
|
|
||
| # ===== DAILY ===== | ||
| @bot.message_handler(func=lambda m:m.text=="🎁 Daily Bonus") | ||
| @require_join | ||
| def daily(m): | ||
| uid=m.from_user.id | ||
| now=int(time.time()) | ||
| c.execute("SELECT last_claim FROM users WHERE user_id=?",(uid,)) | ||
| last=c.fetchone()[0] | ||
| if now-last<86400: | ||
| bot.send_message(m.chat.id,"⏳ wait 24h") | ||
| return | ||
| add_money(uid,0.02) | ||
| c.execute("UPDATE users SET last_claim=? WHERE user_id=?",(now,uid)) | ||
| db.commit() | ||
| bot.send_message(m.chat.id,"🎉 +0.02") | ||
|
|
||
| # ===== TASK ===== | ||
| @bot.message_handler(func=lambda m:m.text=="📋 Tasks") | ||
| @require_join | ||
| def task_btn(m): | ||
| mk=types.InlineKeyboardMarkup() | ||
| mk.add(types.InlineKeyboardButton("Join", url=f"https://t.me/{CHANNEL[1:]}")) | ||
| mk.add(types.InlineKeyboardButton("Check", callback_data="task_check")) | ||
| bot.send_message(m.chat.id,"Complete task to earn $0.02",reply_markup=mk) | ||
|
|
||
| @bot.callback_query_handler(func=lambda c:c.data=="task_check") | ||
| def task_check(ca): | ||
| uid=ca.from_user.id | ||
| if check_join(uid): | ||
| add_money(uid,0.02) | ||
| bot.send_message(uid,"🎉 +$0.02") | ||
| else: | ||
| bot.send_message(uid,"❌ Not done") | ||
|
|
||
| # ===== WITHDRAW ===== | ||
| withdraw_requests={} | ||
| @bot.message_handler(func=lambda m:m.text=="💸 ដកប្រាក់") | ||
| @require_join | ||
| def wd_start(m): | ||
| if get_balance(m.from_user.id)<0.25: | ||
| bot.send_message(m.chat.id,"Min withdraw $0.25") | ||
| return | ||
| bot.send_message(m.chat.id,"📸 Send QR code") | ||
|
|
||
| @bot.message_handler(content_types=['photo']) | ||
| def get_qr(m): | ||
| withdraw_requests[m.from_user.id]={"qr":m.photo[-1].file_id} | ||
| bot.send_message(m.chat.id,"💰 Amount?") | ||
|
|
||
| @bot.message_handler(func=lambda m:m.text.replace('.','',1).isdigit()) | ||
| def get_amount(m): | ||
| uid=m.from_user.id | ||
| if uid not in withdraw_requests: return | ||
| amount=float(m.text) | ||
| if amount>get_balance(uid): | ||
| bot.send_message(m.chat.id,"❌ Not enough") | ||
| return | ||
| withdraw_requests[uid]["amount"]=amount | ||
| mk=types.InlineKeyboardMarkup() | ||
| mk.add(types.InlineKeyboardButton("✅",callback_data=f"wd_done_{uid}")) | ||
| mk.add(types.InlineKeyboardButton("❌",callback_data=f"wd_no_{uid}")) | ||
| bot.send_photo(ADMIN_GROUP_ID,withdraw_requests[uid]["qr"],caption=f"User:{uid} ${amount}",reply_markup=mk) | ||
| bot.send_message(m.chat.id,"Sent to admin") | ||
|
|
||
| @bot.callback_query_handler(func=lambda c:c.data.startswith("wd_")) | ||
| def wd_cb(ca): | ||
| _,act,uid=ca.data.split("_") | ||
| uid=int(uid) | ||
| if uid not in withdraw_requests: return | ||
| amt=withdraw_requests[uid]["amount"] | ||
| if act=="done": | ||
| sub_money(uid,amt) | ||
| bot.send_message(uid,f"✅ Paid ${amt}") | ||
| else: | ||
| bot.send_message(uid,"❌ Cancel") | ||
| del withdraw_requests[uid] | ||
| bot.answer_callback_query(ca.id) | ||
|
|
||
| # ===== ADMIN PANEL ===== | ||
| @bot.message_handler(func=lambda m:m.text=="⚙️ Admin Panel") | ||
| def admin_panel(m): | ||
| if not is_admin(m.from_user.id): return | ||
| mk=types.ReplyKeyboardMarkup(resize_keyboard=True) | ||
| mk.row("Users","Add") | ||
| mk.row("Broadcast","Ban") | ||
| mk.row("Unban","Back") | ||
| bot.send_message(m.chat.id,"Admin Panel",reply_markup=mk) | ||
|
|
||
| # ===== RUN BOT ===== | ||
| bot.infinity_polling() |
There was a problem hiding this comment.
Critical: Python code incorrectly added to TOML configuration file.
railway.toml is a TOML configuration file for Railway deployment. Lines 3-256 contain Python code that will cause a TOML parsing error and break the deployment entirely. Railway will fail to parse this file.
Additionally, this code duplicates the bot implementation that already exists in main.py (which properly uses environment variables). The startCommand on line 2 already points to main.py.
This entire Python block (lines 3-256) must be removed from this file. If you intended to modify the bot implementation, those changes should go in main.py instead.
🐛 Proposed fix: Remove all Python code from railway.toml
[deploy]
startCommand = "uv run python main.py"
-import telebot
-from telebot import types
-import sqlite3
-import time
-
-# ===== CONFIG =====
-TOKEN = "7293546307:AAHKAYnZpKouA6JU9n7-IiEtMzbd_CqCOXI"
-CHANNEL = "@virostore34"
-BOT_USERNAME = "virostore_bot"
-ADMIN_ID = 5099884611
-ADMIN_GROUP_ID = -1003732131155
-
-bot = telebot.TeleBot(TOKEN)
-... (remove all remaining Python code through line 256)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@railway.toml` around lines 1 - 256, railway.toml contains an entire Python
bot implementation (including TOKEN, bot = telebot.TeleBot(...),
bot.infinity_polling(), etc.) which must be removed so the file remains valid
TOML; delete everything except the TOML keys (leave startCommand = "uv run
python main.py") and move any intended code changes into main.py (do not
duplicate sensitive values like TOKEN in the TOML); ensure railway.toml only
holds configuration and that main.py contains the Python bot implementation and
environment-variable usage.
| TOKEN = "7293546307:AAHKAYnZpKouA6JU9n7-IiEtMzbd_CqCOXI" | ||
| CHANNEL = "@virostore34" | ||
| BOT_USERNAME = "virostore_bot" | ||
| ADMIN_ID = 5099884611 | ||
| ADMIN_GROUP_ID = -1003732131155 |
There was a problem hiding this comment.
Critical security vulnerability: Hardcoded secrets exposed in public repository.
The Telegram bot token, channel name, bot username, admin user ID, and admin group ID are all hardcoded in plain text. This is a severe security issue:
- Line 9: Bot token
7293546307:AAHKAYnZpKouA6JU9n7-IiEtMzbd_CqCOXIis exposed. Anyone with this token can control your bot. - Lines 10-13: Channel, bot username, and admin IDs are exposed.
Immediate action required:
- Revoke this bot token immediately via BotFather (
/revokecommand). - Generate a new token and store it as an environment variable in Railway's dashboard.
- Never commit secrets to version control.
The existing main.py already demonstrates the correct pattern using os.getenv('TELEGRAM_BOT_TOKEN').
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@railway.toml` around lines 9 - 13, The file contains hardcoded secrets
(TOKEN, CHANNEL, BOT_USERNAME, ADMIN_ID, ADMIN_GROUP_ID); replace these literal
values with environment lookups and remove them from the repo: stop using TOKEN
and instead read os.getenv('TELEGRAM_BOT_TOKEN') (as shown in main.py), and read
CHANNEL, BOT_USERNAME, ADMIN_ID, and ADMIN_GROUP_ID from environment variables
(e.g., os.getenv('TELEGRAM_CHANNEL'), os.getenv('TELEGRAM_BOT_USERNAME'),
os.getenv('TELEGRAM_ADMIN_ID'), os.getenv('TELEGRAM_ADMIN_GROUP_ID')), update
any code that references these constants to use those env vars, revoke the
exposed token immediately and rotate it, and ensure the railway.toml no longer
contains secrets before committing.
| db = sqlite3.connect("users.db", check_same_thread=False) | ||
| c = db.cursor() |
There was a problem hiding this comment.
Thread-safety issue with SQLite connection.
Even if this code were in the correct file, using a single sqlite3 connection with check_same_thread=False across multiple threads (which telebot uses internally) without proper synchronization can lead to data corruption. The sqlite3 module is not thread-safe for concurrent writes without explicit locking.
Consider using a connection pool or creating connections per-request pattern.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@railway.toml` around lines 18 - 19, The current use of a single sqlite3
connection (db = sqlite3.connect(...)) and cursor (c = db.cursor()) with
check_same_thread=False is unsafe for telebot’s multithreaded handlers; replace
this pattern by creating a fresh sqlite3 connection per request/handler (call
sqlite3.connect(...) inside the handler or a helper like get_db_connection()) or
implement a connection pool and ensure writes are serialized (e.g., via a
dedicated DB worker or threading.Lock around writes). Update all code that uses
the global db or c to obtain and close a local connection/cursor (or use context
managers), and remove reliance on the global db/db cursor to prevent
concurrent-write corruption.
| @bot.callback_query_handler(func=lambda c:c.data=="task_check") | ||
| def task_check(ca): | ||
| uid=ca.from_user.id | ||
| if check_join(uid): | ||
| add_money(uid,0.02) | ||
| bot.send_message(uid,"🎉 +$0.02") | ||
| else: | ||
| bot.send_message(uid,"❌ Not done") |
There was a problem hiding this comment.
Unlimited task reward exploit.
The task_check callback grants $0.02 every time a user clicks "Check" if they're joined to the channel. There's no tracking to prevent users from claiming this reward multiple times, unlike the daily bonus which has last_claim tracking.
This allows users to farm unlimited balance by repeatedly clicking the button.
🐛 Suggested approach (if this code were in main.py)
Add a task_claimed column to the users table and check it before granting the reward, similar to how joined is used for the initial join bonus.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@railway.toml` around lines 192 - 199, The task_check callback currently
awards $0.02 on every click; to prevent farming, add persistent claim tracking
like the daily bonus: add a task_claimed (boolean or timestamp) column to the
users table, update the check in task_check (function name task_check) to call
check_join(uid) AND verify task_claimed is false (or last_task_claim is older
than allowed), only call add_money(uid,0.02) and send success if not previously
claimed, then set task_claimed = true (or update last_task_claim) in the same
transaction; update any DB migration/ORM model and places that read/write user
state to use task_claimed/last_task_claim consistently.
| @bot.message_handler(content_types=['photo']) | ||
| def get_qr(m): | ||
| withdraw_requests[m.from_user.id]={"qr":m.photo[-1].file_id} | ||
| bot.send_message(m.chat.id,"💰 Amount?") | ||
|
|
||
| @bot.message_handler(func=lambda m:m.text.replace('.','',1).isdigit()) | ||
| def get_amount(m): | ||
| uid=m.from_user.id | ||
| if uid not in withdraw_requests: return | ||
| amount=float(m.text) | ||
| if amount>get_balance(uid): | ||
| bot.send_message(m.chat.id,"❌ Not enough") | ||
| return | ||
| withdraw_requests[uid]["amount"]=amount | ||
| mk=types.InlineKeyboardMarkup() | ||
| mk.add(types.InlineKeyboardButton("✅",callback_data=f"wd_done_{uid}")) | ||
| mk.add(types.InlineKeyboardButton("❌",callback_data=f"wd_no_{uid}")) | ||
| bot.send_photo(ADMIN_GROUP_ID,withdraw_requests[uid]["qr"],caption=f"User:{uid} ${amount}",reply_markup=mk) | ||
| bot.send_message(m.chat.id,"Sent to admin") |
There was a problem hiding this comment.
Withdraw flow has no state machine validation.
The withdrawal flow relies on withdraw_requests dict but any photo sent by any user triggers get_qr, and any numeric message triggers get_amount. This creates several issues:
- A user could send a photo without initiating withdrawal (bypassing balance check).
- The flow can be interrupted or interleaved by other users.
- No timeout mechanism for abandoned withdrawal requests.
Consider implementing a proper state machine per user to track the withdrawal flow stages.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@railway.toml` around lines 211 - 229, The withdraw flow lacks per-user state
validation: modify handlers (get_qr, get_amount) to use a per-user state machine
stored in withdraw_requests keyed by uid with a state field (e.g.,
"awaiting_qr", "awaiting_amount") and a timestamp; on photo receipt in get_qr
verify the sender's current state is "awaiting_qr" before saving qr and
advancing to "awaiting_amount", and in get_amount verify state is
"awaiting_amount" before parsing amount, checking get_balance(uid), sending to
ADMIN_GROUP_ID and clearing the entry; add a timeout/cleanup routine that
removes stale withdraw_requests entries after N minutes to prevent abandoned or
interleaved flows.
Summary by CodeRabbit