Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save refet-crypto/a57fb9c42615df143dabc2ab2af09eba to your computer and use it in GitHub Desktop.

Select an option

Save refet-crypto/a57fb9c42615df143dabc2ab2af09eba to your computer and use it in GitHub Desktop.
"""
Greengo Exchange Telegram Bot — с балансом пользователей
Требования: pip install aiogram aiohttp
Запуск: python bot.py
"""
import asyncio
import logging
import sqlite3
import aiohttp
from aiogram import Bot, Dispatcher, F
from aiogram.filters import CommandStart
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import (
Message, CallbackQuery,
InlineKeyboardMarkup, InlineKeyboardButton
)
# ─── Настройки ───────────────────────────────────────────────────────────────
BOT_TOKEN = "8552275284:AAEcVri96NGxtDzt7DlBg9Nm0EWmWokJmto"
API_SECRET = "GG4qggukKiUIR2Iq6p9DSsQFdUDcfb2XYG4mgbQY9upClA00Z8Vl1bZQl"
GREENGO_API = "https://api.greengo.cc/api/v2"
POLL_INTERVAL = 30 # секунд между автопроверками
# Специальный «технический» адрес для заявок пополнения баланса
# Greengo требует wallet в запросе — используем минимально валидный BTC-адрес
# Реальные рубли клиента оседают на твоём балансе в Greengo
DEPOSIT_WALLET = "1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf6a" # Genesis block address (публичный, только для формата)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher(storage=MemoryStorage())
# ─── База данных ─────────────────────────────────────────────────────────────
def db_connect():
conn = sqlite3.connect("bot.db")
conn.row_factory = sqlite3.Row
return conn
def db_init():
with db_connect() as conn:
conn.executescript("""
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
username TEXT,
balance REAL DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
type TEXT,
amount REAL,
order_id TEXT,
note TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS orders (
order_id TEXT PRIMARY KEY,
user_id INTEGER,
type TEXT,
amount_rub REAL,
amount_crypto TEXT,
crypto TEXT,
payment_method TEXT,
wallet TEXT,
status TEXT DEFAULT 'unconfirmed',
created_at TEXT DEFAULT (datetime('now'))
);
""")
def get_or_create_user(user_id: int, username: str = ""):
with db_connect() as conn:
user = conn.execute("SELECT * FROM users WHERE user_id=?", (user_id,)).fetchone()
if not user:
conn.execute("INSERT INTO users (user_id,username) VALUES (?,?)", (user_id, username or ""))
conn.commit()
return dict(user) if user else {"balance": 0.0}
def get_balance(user_id: int) -> float:
with db_connect() as conn:
row = conn.execute("SELECT balance FROM users WHERE user_id=?", (user_id,)).fetchone()
return float(row["balance"]) if row else 0.0
def update_balance(user_id: int, delta: float, order_id: str, tx_type: str, note: str = ""):
with db_connect() as conn:
conn.execute("UPDATE users SET balance=balance+? WHERE user_id=?", (delta, user_id))
conn.execute(
"INSERT INTO transactions (user_id,type,amount,order_id,note) VALUES (?,?,?,?,?)",
(user_id, tx_type, delta, order_id, note)
)
conn.commit()
def get_history(user_id: int, limit: int = 10):
with db_connect() as conn:
return conn.execute(
"SELECT * FROM transactions WHERE user_id=? ORDER BY created_at DESC LIMIT ?",
(user_id, limit)
).fetchall()
def save_order(order_id, user_id, order_type, amount_rub, amount_crypto, crypto, payment_method, wallet):
with db_connect() as conn:
conn.execute(
"INSERT OR REPLACE INTO orders (order_id,user_id,type,amount_rub,amount_crypto,crypto,payment_method,wallet) VALUES (?,?,?,?,?,?,?,?)",
(order_id, user_id, order_type, amount_rub, amount_crypto, crypto, payment_method, wallet)
)
conn.commit()
def update_order_status(order_id: str, status: str):
with db_connect() as conn:
conn.execute("UPDATE orders SET status=? WHERE order_id=?", (status, order_id))
conn.commit()
def get_order(order_id: str):
with db_connect() as conn:
row = conn.execute("SELECT * FROM orders WHERE order_id=?", (order_id,)).fetchone()
return dict(row) if row else None
# ─── FSM ─────────────────────────────────────────────────────────────────────
class DepositStates(StatesGroup):
choosing_payment = State()
entering_amount = State()
class ExchangeStates(StatesGroup):
choosing_direction = State()
choosing_payment = State()
entering_amount = State()
entering_wallet = State()
# ─── Greengo API ─────────────────────────────────────────────────────────────
def greengo_headers():
return {"Api-Secret": API_SECRET, "Content-Type": "application/json"}
async def get_directions():
async with aiohttp.ClientSession() as s:
async with s.get(f"{GREENGO_API}/directions", headers=greengo_headers()) as r:
data = await r.json()
return [d for d in data if d.get("status") == 1]
async def create_order(payment_method: str, wallet: str, amount: str):
async with aiohttp.ClientSession() as s:
async with s.post(
f"{GREENGO_API}/order/create",
headers=greengo_headers(),
json={"payment_method": payment_method, "wallet": wallet, "from_amount": amount}
) as r:
return await r.json()
async def check_order_api(order_id: str):
async with aiohttp.ClientSession() as s:
async with s.post(
f"{GREENGO_API}/order/check",
headers=greengo_headers(),
json={"order_id": [order_id]}
) as r:
return await r.json()
async def cancel_order_api(order_id: str):
async with aiohttp.ClientSession() as s:
async with s.post(
f"{GREENGO_API}/order/cancel",
headers=greengo_headers(),
json={"order_id": [order_id]}
) as r:
return await r.json()
# ─── Автопроверка статусов ───────────────────────────────────────────────────
active_orders: dict = {}
async def poll_orders():
while True:
await asyncio.sleep(POLL_INTERVAL)
if not active_orders:
continue
order_ids = list(active_orders.keys())
try:
async with aiohttp.ClientSession() as s:
async with s.post(
f"{GREENGO_API}/order/check",
headers=greengo_headers(),
json={"order_id": order_ids}
) as r:
result = await r.json()
for order in result.get("data", {}).get("orders", []):
oid = order["order_id"]
status = order["order_status"]
if oid not in active_orders:
continue
info = active_orders[oid]
user_id = info["user_id"]
prev_status = info.get("status")
order_type = info.get("type", "exchange")
if status == prev_status:
continue
active_orders[oid]["status"] = status
update_order_status(oid, status)
# Пополнение баланса
if order_type == "deposit":
if status == "payed":
await bot.send_message(user_id,
f"✅ *Оплата получена!* Зачисляем *{order['amount_payable']} ₽*...",
parse_mode="Markdown")
elif status == "completed":
amt = float(order["amount_payable"])
update_balance(user_id, amt, oid, "deposit", f"Пополнение #{oid}")
bal = get_balance(user_id)
await bot.send_message(user_id,
f"🎉 *Баланс пополнен на {amt:,.0f} ₽!*\n💰 Текущий баланс: *{bal:,.2f} ₽*",
parse_mode="Markdown")
del active_orders[oid]
elif status in ("autocanceled", "canceled"):
await bot.send_message(user_id,
f"❌ *Пополнение отменено*\nЗаявка `{oid}`\n\n/start",
parse_mode="Markdown")
del active_orders[oid]
# Обмен
else:
crypto = info.get("crypto", "")
if status == "payed":
await bot.send_message(user_id,
f"✅ *Оплата получена!* Отправляем {crypto}...",
parse_mode="Markdown")
elif status == "completed":
await bot.send_message(user_id,
f"🎉 *Обмен завершён!*\n📦 {order['amount_receivable']} {crypto} отправлены!\n\n/start",
parse_mode="Markdown")
del active_orders[oid]
elif status in ("autocanceled", "canceled"):
db_order = get_order(oid)
if db_order and db_order["type"] == "exchange_balance":
refund = db_order["amount_rub"]
update_balance(user_id, refund, oid, "refund", f"Возврат за #{oid}")
bal = get_balance(user_id)
await bot.send_message(user_id,
f"❌ *Заявка отменена*\n💰 Возвращено: *{refund:,.0f} ₽*\nБаланс: *{bal:,.2f} ₽*",
parse_mode="Markdown")
else:
await bot.send_message(user_id,
f"❌ Заявка `{oid}` отменена.\n/start", parse_mode="Markdown")
del active_orders[oid]
except Exception as e:
logger.error(f"poll_orders: {e}")
# ─── Константы ───────────────────────────────────────────────────────────────
PAYMENT_LABELS = {
"card": "💳 Карта РФ",
"sbp": "📱 СБП",
"sbp_alpha": "🏦 СБП → Альфа-Банк",
"sbp_sber": "🏦 СБП → Сбербанк",
"sbp_ozon": "🏦 СБП → Ozon Банк",
"sbp_gazprom": "🏦 СБП → Газпромбанк",
"sbp_psb": "🏦 СБП → ПСБ",
"sbp_sovkom": "🏦 СБП → Совкомбанк",
"sbp_otp": "🏦 СБП → ОТП Банк",
"sbp_transgran": "🌍 СБП трансграничный",
"kzt": "🇰🇿 Карта Казахстана",
"mobile": "📞 Мобильный счёт",
"qr_code": "🔲 QR-код",
}
STATUS_LABELS = {
"unconfirmed": "⏳ Создана",
"awaiting": "⌛ Ожидает оплаты",
"payed": "✅ Оплачена, обрабатывается",
"completed": "🎉 Выполнена",
"autocanceled": "❌ Отменена автоматически",
"canceled": "🚫 Отменена",
}
TX_LABELS = {"deposit": "➕", "exchange": "💱", "exchange_balance": "💱", "refund": "↩️"}
def parse_crypto(fullname: str) -> str:
return fullname.split(" на ")[-1] if " на " in fullname else fullname
def main_kb():
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="💰 Мой баланс", callback_data="my_balance")],
[InlineKeyboardButton(text="➕ Пополнить баланс", callback_data="deposit")],
[InlineKeyboardButton(text="💱 Обменять → крипта", callback_data="start_exchange")],
[InlineKeyboardButton(text="📋 История", callback_data="history")],
[InlineKeyboardButton(text="📊 Курсы", callback_data="show_rates")],
])
# ─── /start ──────────────────────────────────────────────────────────────────
@dp.message(CommandStart())
async def cmd_start(message: Message, state: FSMContext):
await state.clear()
get_or_create_user(message.from_user.id, message.from_user.username)
bal = get_balance(message.from_user.id)
await message.answer(
f"👋 Привет, *{message.from_user.first_name}*!\n\n"
f"💰 Баланс: *{bal:,.2f} ₽*\n\nВыбери действие:",
parse_mode="Markdown", reply_markup=main_kb()
)
@dp.callback_query(F.data == "back_start")
async def back_start(call: CallbackQuery, state: FSMContext):
await state.clear()
await call.answer()
bal = get_balance(call.from_user.id)
await call.message.edit_text(
f"💰 Баланс: *{bal:,.2f} ₽*\n\nВыбери действие:",
parse_mode="Markdown", reply_markup=main_kb()
)
# ─── Баланс ──────────────────────────────────────────────────────────────────
@dp.callback_query(F.data == "my_balance")
async def show_balance(call: CallbackQuery):
await call.answer()
bal = get_balance(call.from_user.id)
kb = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➕ Пополнить", callback_data="deposit")],
[InlineKeyboardButton(text="💱 Обменять → крипта", callback_data="start_exchange")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_start")],
])
await call.message.edit_text(
f"💰 *Твой баланс*\n\n*{bal:,.2f} ₽*",
parse_mode="Markdown", reply_markup=kb
)
# ─── История ─────────────────────────────────────────────────────────────────
@dp.callback_query(F.data == "history")
async def show_history(call: CallbackQuery):
await call.answer()
txs = get_history(call.from_user.id)
if not txs:
text = "📋 *История транзакций*\n\nПока пусто."
else:
text = "📋 *Последние транзакции:*\n\n"
for tx in txs:
icon = TX_LABELS.get(tx["type"], "•")
sign = "+" if tx["amount"] > 0 else ""
date = tx["created_at"][:16]
text += f"{icon} {sign}{tx['amount']:,.0f} ₽ {date}\n"
kb = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_start")]
])
await call.message.edit_text(text, parse_mode="Markdown", reply_markup=kb)
# ─── Курсы ───────────────────────────────────────────────────────────────────
@dp.callback_query(F.data == "show_rates")
async def show_rates(call: CallbackQuery):
await call.answer()
await call.message.edit_text("⏳ Загружаю...")
try:
directions = await get_directions()
text = "📊 *Актуальные курсы:*\n\n"
seen = {}
for d in directions:
crypto = parse_crypto(d["fullname"])
if crypto not in seen:
seen[crypto] = True
text += (
f"🪙 *{crypto}*\n"
f" Курс: `{float(d['current_course']):,.2f}` ₽\n"
f" Комиссия: {d['comission_percent']}%\n"
f" Мин: {d['min_amount']} ₽ | Макс: {d['max_amount']:,} ₽\n\n"
)
kb = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="💱 Начать обмен", callback_data="start_exchange")],
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_start")],
])
await call.message.edit_text(text, parse_mode="Markdown", reply_markup=kb)
except Exception as e:
await call.message.edit_text(f"❌ Ошибка: {e}")
# ─── Пополнение баланса ──────────────────────────────────────────────────────
@dp.callback_query(F.data == "deposit")
async def deposit_start(call: CallbackQuery, state: FSMContext):
await call.answer()
buttons = [[InlineKeyboardButton(text=lbl, callback_data=f"dep_pay:{key}")]
for key, lbl in PAYMENT_LABELS.items()]
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="back_start")])
await state.set_state(DepositStates.choosing_payment)
await call.message.edit_text(
"➕ *Пополнение баланса*\n\nВыбери способ оплаты:",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)
)
@dp.callback_query(DepositStates.choosing_payment, F.data.startswith("dep_pay:"))
async def deposit_amount(call: CallbackQuery, state: FSMContext):
await call.answer()
pm = call.data.split(":", 1)[1]
await state.update_data(payment_method=pm)
label = PAYMENT_LABELS.get(pm, pm)
await state.set_state(DepositStates.entering_amount)
await call.message.edit_text(
f"✅ Способ: *{label}*\n\nВведи сумму пополнения в рублях (мин. 300 ₽):",
parse_mode="Markdown"
)
@dp.message(DepositStates.entering_amount)
async def deposit_create(message: Message, state: FSMContext):
text = message.text.strip().replace(" ", "").replace(",", ".")
try:
amount = float(text)
if amount < 300:
await message.answer("❌ Минимум 300 ₽:")
return
except ValueError:
await message.answer("❌ Введи число, например: 1000")
return
data = await state.get_data()
await message.answer("⏳ Создаю заявку...")
try:
result = await create_order(data["payment_method"], DEPOSIT_WALLET, str(int(amount)))
if result.get("response") != "success":
await message.answer(f"❌ {result.get('message', 'Ошибка')}\n\n/start")
await state.clear()
return
order = result["items"][0]
order_id = order["order_id"]
save_order(order_id, message.from_user.id, "deposit",
float(order["amount_payable"]), "0", "RUB",
data["payment_method"], DEPOSIT_WALLET)
active_orders[order_id] = {
"user_id": message.from_user.id,
"type": "deposit",
"status": "unconfirmed"
}
payment_info = order.get("wallet_payment", "—")
bank = order.get("bank_name", "")
fast_link = order.get("fast_link", "")
msg = (
f"✅ *Заявка на пополнение создана!*\n\n"
f"🆔 ID: `{order_id}`\n"
f"💸 К оплате: *{order['amount_payable']} ₽*\n\n"
)
if bank: msg += f"🏦 Банк: {bank}\n"
msg += f"💳 Реквизиты:\n`{payment_info}`\n\n"
if fast_link: msg += f"🔲 [QR-код]({fast_link})\n\n"
msg += "⏳ После оплаты баланс пополнится автоматически."
kb = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔄 Проверить", callback_data=f"check:{order_id}")],
[InlineKeyboardButton(text="❌ Отменить", callback_data=f"cancel:{order_id}")],
])
await message.answer(msg, parse_mode="Markdown", reply_markup=kb)
await state.clear()
except Exception as e:
await message.answer(f"❌ Ошибка: {e}\n\n/start")
await state.clear()
# ─── Обмен → крипта ──────────────────────────────────────────────────────────
@dp.callback_query(F.data == "start_exchange")
async def exchange_start(call: CallbackQuery, state: FSMContext):
await call.answer()
await call.message.edit_text("⏳ Загружаю направления...")
try:
directions = await get_directions()
cryptos = {}
for d in directions:
c = parse_crypto(d["fullname"])
if c not in cryptos:
cryptos[c] = d["id"]
buttons = [[InlineKeyboardButton(text=f"🪙 {c}", callback_data=f"crypto:{did}:{c}")]
for c, did in cryptos.items()]
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="back_start")])
await state.set_state(ExchangeStates.choosing_direction)
await call.message.edit_text(
"💱 *Обмен → крипта*\n\nВыбери криптовалюту:",
parse_mode="Markdown",
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment