-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
136 lines (118 loc) · 5.03 KB
/
app.py
File metadata and controls
136 lines (118 loc) · 5.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import asyncio
from aiogram import Bot, Dispatcher, F
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.types import Message, CallbackQuery, FSInputFile, BufferedInputFile
from aiogram.filters import CommandStart
from pathlib import Path
from settings import BOT_TOKEN, DATA_DIR, PRODUCT_A_DIR, PRODUCT_B_DIR, PRODUCT_C_DIR, log
from db import DB
from inventory import Inventory
from keyboards import main_menu, product_actions
from bank import build_qr_for_user
from aiogram.filters import Command
bot = Bot(BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
dp = Dispatcher()
db = DB(DATA_DIR / 'shop.sqlite')
inv = Inventory({
'A': Path(PRODUCT_A_DIR),
'B': Path(PRODUCT_B_DIR),
'C': Path(PRODUCT_C_DIR),
})
@dp.message(CommandStart())
async def start(m: Message):
db.upsert_user(m.from_user.id, m.from_user.username or '', m.from_user.first_name or '')
text = (f"CHÀO MỪNG {m.from_user.first_name or 'bạn'}\n"
f"ID: <code>{m.from_user.id}</code>\n\n"
f"Kho: A={inv.count_items('A')} | B={inv.count_items('B')} | C={inv.count_items('C')}\n"
f"Chọn mặt hàng:")
await m.answer(text, reply_markup=main_menu())
@dp.message(Command('nap'))
async def nap(m: Message):
parts = (m.text or '').split()
if len(parts) < 2:
await m.reply("Dùng: /nap <sotien>")
return
try:
amount = int(parts[1])
if amount <= 0:
raise ValueError
except Exception:
await m.reply("Số tiền không hợp lệ.")
return
path, caption = build_qr_for_user(m.from_user.id, amount)
# Caption may include HTML (e.g., bold VND)
await m.reply_photo(photo=FSInputFile(path), caption=caption, parse_mode='HTML')
@dp.callback_query(F.data.startswith('back:'))
async def back_main(cq: CallbackQuery):
await cq.message.edit_text(
f"Kho: A={inv.count_items('A')} | B={inv.count_items('B')} | C={inv.count_items('C')}\nChọn mặt hàng:",
reply_markup=main_menu()
)
await cq.answer()
@dp.callback_query(F.data.startswith('prod:'))
async def product_menu(cq: CallbackQuery):
product = cq.data.split(':')[1]
stock = inv.count_items(product)
await cq.message.edit_text(f"Mặt hàng {product} — tồn: {stock}\nChọn số lượng:", reply_markup=product_actions(product))
await cq.answer()
@dp.callback_query(F.data.startswith('qty:'))
async def ask_qty(cq: CallbackQuery):
product = cq.data.split(':')[1]
await cq.message.edit_text(f"Nhập số lượng muốn mua cho mặt hàng {product} (trả lời tin nhắn này bằng số)." )
# save state by pinning product to message via caption entities? use simple approach: store in message
cq.message.conf = {'await_qty_for': product}
await cq.answer()
# Fallback: parse user reply to quantity after prompt
@dp.message()
async def any_msg(m: Message):
# crude state check: if user replied to bot message containing 'Nhập số lượng'
if m.reply_to_message and 'Nhập số lượng muốn mua' in (m.reply_to_message.text or ''):
product = next((ch for ch in ['A','B','C'] if f'mặt hàng {ch}' in (m.reply_to_message.text or '')), 'A')
try:
qty = int(m.text.strip())
if qty <= 0:
raise ValueError
except Exception:
await m.reply('Số lượng không hợp lệ. Vui lòng nhập số nguyên > 0.')
return
await process_purchase(m, product, qty)
else:
await m.reply("Dùng /start để mở menu.")
@dp.callback_query(F.data.startswith('buy:'))
async def quick_buy(cq: CallbackQuery):
_, product, qty = cq.data.split(':')
qty = int(qty)
# Respond immediately to remove Telegram's loading state
try:
await cq.answer('Đang xử lý...')
except Exception:
pass
await process_purchase(cq.message, product, qty, from_callback=cq)
async def process_purchase(target_msg_or_cq_message, product: str, qty: int, from_callback: CallbackQuery|None=None):
chat_id = target_msg_or_cq_message.chat.id
user = target_msg_or_cq_message.from_user
try:
_filename, data = await inv.allocate(product, qty)
except ValueError as e:
if from_callback:
await from_callback.answer(str(e), show_alert=True)
else:
await bot.send_message(chat_id, str(e))
return
send_name = f"{product}-{qty}-{user.id}.txt"
# Send from memory buffer for speed (avoid disk I/O)
await bot.send_document(
chat_id,
document=BufferedInputFile(data, filename=send_name),
caption=f"Đã giao {qty} mục hàng {product}"
)
# Log to DB in background to reduce latency
try:
await asyncio.to_thread(db.add_order, user.id, product, qty, send_name)
except Exception:
# fallback synchronous
db.add_order(user.id, product, qty, send_name)
log.info(f"Order user={user.id} product={product} qty={qty}")
if __name__ == '__main__':
asyncio.run(dp.start_polling(bot))