Skip to content

railway.toml - #44

Open
doreamon2343-cmyk wants to merge 1 commit into
aeither:mainfrom
doreamon2343-cmyk:patch-2
Open

doreamon2343-cmyk wants to merge 1 commit into
aeither:mainfrom
doreamon2343-cmyk:patch-2

Conversation

@doreamon2343-cmyk

@doreamon2343-cmyk doreamon2343-cmyk commented Mar 26, 2026 •

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features
    • Added Telegram bot with user registration and channel membership verification
    • Wallet system with balance tracking and referral rewards
    • Daily bonus claims with cooldown
    • Task-based reward system
    • Withdrawal requests with QR-code processing and admin approval
    • Admin panel for bot management

@coderabbitai

coderabbitai Bot commented Mar 26, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Telegram Bot Implementation
main.py
Added bot configuration (token, channel ID, admin IDs), SQLite user table with balance/claim tracking, user management functions (create, check join/ban status, balance mutations). Implemented /start command with referral argument parsing and channel membership enforcement. Added callback handlers for initial balance claims, /balance, /invite, /daily, /tasks, /withdraw flows with QR-code requests and admin panel controls. Bot polls indefinitely via infinity_polling().

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hop, hop! A bot springs to life,
With SQLite storing user delight,
Referrals bloom, balances grow,
Admins approve the withdrawal flow,
Channel guards keep spammers at bay! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'railway.toml' is just a filename and does not describe the actual changes made in the pull request, which involve adding a complete Telegram bot implementation. Change the title to describe the main change, such as 'Add Telegram bot implementation with user management and balance system' or 'Implement Telegram bot with database and admin features'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

@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: 5

🧹 Nitpick comments (1)
railway.toml (1)

74-75: Bare except clauses hide errors.

Multiple bare except: clauses (lines 74, 112, 149) silently swallow all exceptions including KeyboardInterrupt, SystemExit, and programming errors. This makes debugging difficult and can mask serious issues.

♻️ Suggested improvement
-    except:
-        return False
+    except Exception:
+        return False

Or 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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ad6252d3-eab8-42ac-8227-9e3ee7877daa

📥 Commits

Reviewing files that changed from the base of the PR and between 5cfd740 and 992aedf.

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

Comment thread railway.toml
Comment on lines 1 to +256
[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()

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

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.

Comment thread railway.toml
Comment on lines +9 to +13
TOKEN = "7293546307:AAHKAYnZpKouA6JU9n7-IiEtMzbd_CqCOXI"
CHANNEL = "@virostore34"
BOT_USERNAME = "virostore_bot"
ADMIN_ID = 5099884611
ADMIN_GROUP_ID = -1003732131155

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

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_CqCOXI is exposed. Anyone with this token can control your bot.
  • Lines 10-13: Channel, bot username, and admin IDs are exposed.

Immediate action required:

  1. Revoke this bot token immediately via BotFather (/revoke command).
  2. Generate a new token and store it as an environment variable in Railway's dashboard.
  3. 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.

Comment thread railway.toml
Comment on lines +18 to +19
db = sqlite3.connect("users.db", check_same_thread=False)
c = db.cursor()

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

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.

Comment thread railway.toml
Comment on lines +192 to +199
@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")

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

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.

Comment thread railway.toml
Comment on lines +211 to +229
@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")

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 | 🟡 Minor

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:

  1. A user could send a photo without initiating withdrawal (bypassing balance check).
  2. The flow can be interrupted or interleaved by other users.
  3. 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.

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