TON'da ödemeli vitrin botu
Bu makalede, bir Telegram botunda ödeme kabul etme sürecini anlatacağız.
📖 Öğrenecekleriniz
Bu makalede şunları öğreneceksiniz:
- Python + Aiogram kullanarak bir Telegram botu oluşturmak
- Genel TON API'si (TON Center) ile çalışmak
- SQlite veritabanı ile çalışmak
Ve nihayetinde: Önceki adımlardan elde edilen bilgilerle bir Telegram botunda ödeme kabul etme yöntemini.
📚 Başlamadan önce
En son Python sürümünü kurduğunuzdan ve aşağıdaki paketleri yüklediğinizden emin olun:
- aiogram
- requests
- sqlite3
🚀 Hadi başlayalım!
Aşağıdaki sırayı takip edeceğiz:
- SQlite veritabanı ile çalışmak
- Genel TON API'si (TON Center) ile çalışmak
- Python + Aiogram kullanarak bir Telegram botu oluşturmak
- Kazanç!
Proje dizinimizde aşağıdaki dört dosyayı oluşturalım:
telegram-bot
├── config.json
├── main.py
├── api.py
└── db.py
Yapılandırma
config.json
dosyasında bot tokenimizi ve genel TON API anahtarımızı saklayacağız.
{
"BOT_TOKEN": "Bot tokeniniz",
"MAINNET_API_TOKEN": "Mainnet API tokeniniz",
"TESTNET_API_TOKEN": "Testnet API tokeniniz",
"MAINNET_WALLET": "Mainnet cüzdanınız",
"TESTNET_WALLET": "Testnet cüzdanınız",
"WORK_MODE": "testnet"
}
config.json
dosyasında hangi ağı kullanacağımıza karar veriyoruz: testnet
veya mainnet
.
Veritabanı
Veritabanı oluşturma
Bu örnek yerel bir Sqlite veritabanı kullanmaktadır.
db.py
dosyasını oluşturun.
Veritabanı ile çalışmaya başlamak için sqlite3 modülünü ve zamanla çalışmak için bazı modülleri içe aktarmamız gerekiyor.
import sqlite3
import datetime
import pytz
sqlite3
—sqlite veritabanı ile çalışmak için modüldatetime
—zamanla çalışmak için modülpytz
—zaman dilimleri ile çalışmak için modül
Sonraki adımda veritabanına bağlanmak ve onunla çalışmak için bir cursor oluşturmamız gerekiyor:
locCon = sqlite3.connect('local.db', check_same_thread=False)
cur = locCon.cursor()
Eğer veritabanı mevcut değilse, otomatik olarak oluşturulacaktır.
Artık tabloları oluşturabiliriz. İki tane tablomuz var.
İşlemler:
CREATE TABLE transactions (
source VARCHAR (48) NOT NULL,
hash VARCHAR (50) UNIQUE
NOT NULL,
value INTEGER NOT NULL,
comment VARCHAR (50)
);
source
—ödeme yapanın cüzdan adresihash
—işlem hash'ivalue
—işlem değericomment
—işlem notu
Kullanıcılar:
CREATE TABLE users (
id INTEGER UNIQUE
NOT NULL,
username VARCHAR (33),
first_name VARCHAR (300),
wallet VARCHAR (50) DEFAULT none
);
id
—Telegram kullanıcı ID'siusername
—Telegram kullanıcı adıfirst_name
—Telegram kullanıcısının adıwallet
—kullanıcı cüzdan adresi
Not:
users
tablosunda kullanıcıları saklarız :) Telegram ID'si, @username, adı ve cüzdanı. Cüzdan, ilk başarılı ödemeyle veritabanına eklenir.
transactions
tablosu doğrulanmış işlemleri saklar. Bir işlemi doğrulamak için hash, source, value ve comment'e ihtiyacımız var.
Bu tabloları oluşturmak için aşağıdaki fonksiyonu çalıştırmamız gerekiyor:
cur.execute('''CREATE TABLE IF NOT EXISTS transactions (
source VARCHAR (48) NOT NULL,
hash VARCHAR (50) UNIQUE
NOT NULL,
value INTEGER NOT NULL,
comment VARCHAR (50)
)''')
locCon.commit()
cur.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER UNIQUE
NOT NULL,
username VARCHAR (33),
first_name VARCHAR (300),
wallet VARCHAR (50) DEFAULT none
)''')
locCon.commit()
Bu kod tabloları daha önce oluşturulmamışlarsa oluşturacaktır.
Veritabanı ile çalışma
Durumu analiz edelim: Kullanıcı bir işlem yaptı. Bunu nasıl doğrularız? Aynı işlemin iki kez onaylanmadığından nasıl emin oluruz?
transactions da body_hash var; bu sayede veritabanında bir işlemin olup olmadığını kolayca anlayabiliriz.
Kesin olduğumuz işlemleri veritabanına ekleriz. check_transaction
fonksiyonu, bulunan işlemin veritabanında olup olmadığını kontrol eder.
add_v_transaction
işlemi transactions tablosuna ekler.
def add_v_transaction(source, hash, value, comment):
cur.execute("INSERT INTO transactions (source, hash, value, comment) VALUES (?, ?, ?, ?)",
(source, hash, value, comment))
locCon.commit()
def check_transaction(hash):
cur.execute(f"SELECT hash FROM transactions WHERE hash = '{hash}'")
result = cur.fetchone()
if result:
return True
return False
check_user
, kullanıcının veritabanında olup olmadığını kontrol eder ve değilse ekler.
def check_user(user_id, username, first_name):
cur.execute(f"SELECT id FROM users WHERE id = '{user_id}'")
result = cur.fetchone()
if not result:
cur.execute("INSERT INTO users (id, username, first_name) VALUES (?, ?, ?)",
(user_id, username, first_name))
locCon.commit()
return False
return True
Kullanıcı, tablodaki bir cüzdanı saklayabilir. İlk başarılı alışverişle eklenir. v_wallet
fonksiyonu, kullanıcının ilişkili bir cüzdanı olup olmadığını kontrol eder. Eğer varsa, onu döner. Yoksa ekler.
def v_wallet(user_id, wallet):
cur.execute(f"SELECT wallet FROM users WHERE id = '{user_id}'")
result = cur.fetchone()
if result[0] == "none":
cur.execute(
f"UPDATE users SET wallet = '{wallet}' WHERE id = '{user_id}'")
locCon.commit()
return True
else:
return result[0]
get_user_wallet
basitçe kullanıcının cüzdanını döndürür.
def get_user_wallet(user_id):
cur.execute(f"SELECT wallet FROM users WHERE id = '{user_id}'")
result = cur.fetchone()
return result[0]
get_user_payments
kullanıcının ödeme listesini döndürür. Bu fonksiyon, kullanıcının bir cüzdanı olup olmadığını kontrol eder. Eğer varsa, ödeme listesini döndürür.
def get_user_payments(user_id):
wallet = get_user_wallet(user_id)
if wallet == "none":
return "Cüzdanınız yok"
else:
cur.execute(f"SELECT * FROM transactions WHERE source = '{wallet}'")
result = cur.fetchall()
tdict = {}
tlist = []
try:
for transaction in result:
tdict = {
"value": transaction[2],
"comment": transaction[3],
}
tlist.append(tdict)
return tlist
except:
return False
API
Üçüncü taraf API'leri aracılığıyla blok zinciri ile etkileşim kurma yeteneğine sahibiz. Bu hizmetler sayesinde geliştiriciler kendi düğümlerini çalıştırma ve API'lerini özelleştirme adımını atlayabilir.
Gereken İstekler
Aslında, kullanıcının bize gerekli miktarı transfer ettiğini doğrulamak için neye ihtiyacımız var?
Sadece cüzdanımıza yapılmış en son gelen transferlere bakmamız ve aralarında doğru adresle doğru miktarda (ve belki de benzersiz bir notla) bir işlem bulmamız gerekiyor.
Bunun için TON Center'da bir getTransactions
yöntemi var.
getTransactions
Varsayılan olarak, bunu uygularsak son 10 işlemi alırız. Ancak, daha fazlasını da belirtebiliriz, ancak bu bir yanıtın süresini biraz uzatır. Ve büyük olasılıkla o kadarına ihtiyacınız yoktur.
Önemli: Daha fazla istiyorsanız, her işlemin
lt
vehash
'i vardır. Örneğin, 30 işlemi gözlemleyebilirsiniz; eğer aralarındaki doğru işlem bulunamazsa, son işlemdenlt
vehash
'i alıp isteğe ekleyebilirsiniz.
Böylece bir sonraki 30 işlemi alıyorsunuz, devam edebilirsiniz.
Örneğin, test ağında EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5
cüzdanı var, bazı işlemleri mevcut:
Aşağıdaki sorgu ile bu adresten gelen iki işlemi alacağız (şu anda ihtiyaç duyulmayan bazı bilgiler gizlenmiştir, yukarıdaki bağlantıda tam yanıtı görebilirsiniz).
{
"ok": true,
"result": [
{
"transaction_id": {
"lt": "1944556000003",
"hash": "swpaG6pTBXwYI2024NAisIFp59Fw3k1DRQ5fa5SuKAE="
},
"in_msg": {
"source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5",
"value": "1000000000",
"body_hash": "kBfGYBTkBaooeZ+NTVR0EiVGSybxQdb/ifXCRX5O7e0=",
"message": "Deniz esintisi 🌊"
},
"out_msgs": []
},
{
"transaction_id": {
"lt": "1943166000003",
"hash": "hxIQqn7lYD/c/fNS7W/iVsg2kx0p/kNIGF6Ld0QEIxk="
},
"in_msg": {
"source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5",
"value": "1000000000",
"body_hash": "7iirXn1RtliLnBUGC5umIQ6KTw1qmPk+wwJ5ibh9Pf0=",
"message": "Bahar ormanı 🌲"
},
"out_msgs": []
}
]
}
Bu adresten en son iki işlemi aldık. lt
ve hash
ekleyerek tekrar sorguladığımızda iki işlem daha alacağız. Ancak ikinci işlem bir sonraki işlem olacak. Yani bu adrese ait ikinci ve üçüncü işlemleri alacağız.
{
"ok": true,
"result": [
{
"transaction_id": {
"lt": "1944556000003",
"hash": "hxIQqn7lYD/c/fNS7W/iVsg2kx0p/kNIGF6Ld0QEIxk="
},
"in_msg": {
"source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5",
"value": "1000000000",
"body_hash": "7iirXn1RtliLnBUGC5umIQ6KTw1qmPk+wwJ5ibh9Pf0=",
"message": "Bahar ormanı 🌲"
},
"out_msgs": []
},
{
"transaction_id": {
"lt": "1845458000003",
"hash": "k5U9AwIRNGhC10hHJ3MBOPT//bxAgW5d9flFiwr1Sao="
},
"in_msg": {
"source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5",
"value": "1000000000",
"body_hash": "XpTXquHXP64qN6ihHe7Tokkpy88tiL+5DeqIrvrNCyo=",
"message": "İkinci"
},
"out_msgs": []
}
]
}
İstek şu şekilde olacak bu.
Ayrıca detectAddress
yöntemine de ihtiyacımız var.
Test ağındaki bir Tonkeeper cüzdan adresinin örneği: kQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aCTb
. Explorer'da işlemi ararsak, yukarıdaki adres yerine: EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R
görünüyor.
Bu yöntem bize “doğru” adresi döndürüyor.
{
"ok": true,
"result": {
"raw_form": "0:b3409241010f85ac415cbf13b9b0dc6157d09a39d2bd0827eadb20819f067868",
"bounceable": {
"b64": "EQCzQJJBAQ+FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"b64url": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R"
},
"non_bounceable": {
"b64": "UQCzQJJBAQ+FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aMKU",
"b64url": "UQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aMKU"
}
}
}
b64url
'ye ihtiyacımız var.
Bu yöntem, kullanıcının adresini doğrulamamızı sağlar.
Genel olarak, ihtiyacımız olan her şey bu.
API istekleri ve onlarla ne yapılacağı
IDE'ye geri dönelim. api.py
dosyasını oluşturun.
Gerekli kütüphaneleri içe aktarın.
import requests
import json
# Veritabanına buradan işlemleri eklemek uygun olduğu için db modülümüzü içe aktaracağız
import db
requests
—API'ye istek yapmak içinjson
—json ile çalışmak içindb
—sqlite veritabanımızla çalışmak için
İsteklerin başlangıcı için iki değişken oluşturuyoruz.
# Bu, isteklerimizin başlangıcı
MAINNET_API_BASE = "https://toncenter.com/api/v2/"
TESTNET_API_BASE = "https://testnet.toncenter.com/api/v2/"
config.json dosyasından tüm API tokenlerini ve cüzdanları alın.
# Hangi ağda çalıştığımızı öğren
with open('config.json', 'r') as f:
config_json = json.load(f)
MAINNET_API_TOKEN = config_json['MAINNET_API_TOKEN']
TESTNET_API_TOKEN = config_json['TESTNET_API_TOKEN']
MAINNET_WALLET = config_json['MAINNET_WALLET']
TESTNET_WALLET = config_json['TESTNET_WALLET']
WORK_MODE = config_json['WORK_MODE']
Ağa göre gerekli verileri alıyoruz.
if WORK_MODE == "mainnet":
API_BASE = MAINNET_API_BASE
API_TOKEN = MAINNET_API_TOKEN
WALLET = MAINNET_WALLET
else:
API_BASE = TESTNET_API_BASE
API_TOKEN = TESTNET_API_TOKEN
WALLET = TESTNET_WALLET
İlk istek fonksiyonumuz detectAddress
.
def detect_address(address):
url = f"{API_BASE}detectAddress?address={address}&api_key={API_TOKEN}"
r = requests.get(url)
response = json.loads(r.text)
try:
return response['result']['bounceable']['b64url']
except:
return False
Girişte tahmini bir adresimiz var ve çıktıda ya bizim için gerekli olan “doğru” adresi ya da False döndürüyoruz.
Bir istek sonunda bir API anahtarının eklenmiş olduğunu fark edebilirsiniz. Bu, API'ye yapılan istek sayısını sınırlamak için gereklidir. Bunun olmaması durumunda, sınırlama ile bir istek yapabiliriz.
Sonraki getTransactions
fonksiyonu:
def get_address_transactions():
url = f"{API_BASE}getTransactions?address={WALLET}&limit=30&archival=true&api_key={API_TOKEN}"
r = requests.get(url)
response = json.loads(r.text)
return response['result']
Bu fonksiyon, WALLET
'ımıza yönelik son 30 işlemi döndürmektedir.
Burada archival=true
ifadesi, yalnızca blok zincirinin tam geçmişine sahip bir düğümden işlem almak için gereklidir.
Çıktıda [0], [1], ..., [29] biçiminde işlemler listesi—kısaca bir sözlükler listesi alıyoruz.
Ve nihayet, son fonksiyon:
def find_transaction(user_wallet, value, comment):
# Son 30 işlemi al
transactions = get_address_transactions()
for transaction in transactions:
# Gelen "mesaj" - işlemi seç
msg = transaction['in_msg']
if msg['source'] == user_wallet and msg['value'] == value and msg['message'] == comment:
# Eğer tüm veriler eşleşiyorsa, bu işlemin daha önce doğrulanmadığını kontrol ediyoruz
t = db.check_transaction(msg['body_hash'])
if t == False:
# Eğer değilse, doğrulanmış olanlar tablosuna yazıyoruz
# ve True döndürüyoruz
db.add_v_transaction(
msg['source'], msg['body_hash'], msg['value'], msg['message'])
print("işlem bulundu")
print(
f"işlem kaynağı: {msg['source']} \nDeğer: {msg['value']} \nNot: {msg['message']}")
return True
# Eğer bu işlem zaten doğrulanmışsa, diğerlerini kontrol ediyoruz, belki doğru olanı bulabiliriz
else:
pass
# Son 30 işlemde gerekli olanı bulamazsak, False döndür
# Buraya, sonraki 29 işlemi görme kodu ekleyebilirsiniz
# Ancak, Örnek kapsamı içinde bu gereksiz olur.
return False
Girişte "doğru" cüzdan adresi, miktar ve not bulunmaktadır. Niyet edilen gelen işlem bulunursa, çıktı True; aksi takdirde False olacaktır.
Telegram botu
Öncelikle, bir bot için temeli oluşturalım.
İçe aktarmalar
Bu bölümde gerekli kütüphaneleri içe aktaracağız.
aiogram
'dan Bot
, Dispatcher
, types
ve executor
ihtiyaç duyacağız.
from aiogram import Bot, Dispatcher, executor, types
MemoryStorage
, bilgi geçici olarak saklamak için gereklidir.
FSMContext
, State
ve StatesGroup
, durum makinesi ile çalışmak için gereklidir.
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters.state import State, StatesGroup
json
, json dosyaları ile çalışmak için gereklidir. logging
, hataları kaydetmek için gereklidir.
import json
import logging
api
ve db
, daha sonra dolduracağımız kendi dosyalarımızdır.
import db
import api
Yapılandırma ayarları
BOT_TOKEN
gibi verileri ayrı bir config.json
dosyasında saklamanız önerilir. Böylece ödemeleri almak için cüzdanlarınızı daha rahat kurabilirsiniz.
{
"BOT_TOKEN": "Bot tokeniniz",
"MAINNET_API_TOKEN": "Mainnet API tokeniniz",
"TESTNET_API_TOKEN": "Testnet API tokeniniz",
"MAINNET_WALLET": "Mainnet cüzdanınız",
"TESTNET_WALLET": "Testnet cüzdanınız",
"WORK_MODE": "testnet"
}
Bot tokeni
BOT_TOKEN
, @BotFather ile aldığınız Telegram bot tokenidir.
Çalışma modu
WORK_MODE
anahtarında, botun çalışma modunu tanımlayacağız—test veya ana ağda; sırasıyla testnet
veya mainnet
seçeneklerinden biri.
API tokenleri
*_API_TOKEN
için API tokenleri TON Center botlarından elde edilebilir:
- mainnet için — @tonapibot
- testnet için — @tontestnetapibot
Yapılandırmayı botumuza bağlama
Sonraki adımda botun ayarlarını tamamlayalım.
config.json
dosyasından botun çalışması için gerekli tokeni alalım:
with open('config.json', 'r') as f:
config_json = json.load(f)
BOT_TOKEN = config_json['BOT_TOKEN']
# Ödemeleri almak için buraya cüzdanlarınızı yerleştirin
MAINNET_WALLET = config_json['MAINNET_WALLET']
TESTNET_WALLET = config_json['TESTNET_WALLET']
WORK_MODE = config_json['WORK_MODE']
if WORK_MODE == "mainnet":
WALLET = MAINNET_WALLET
else:
# Varsayılan olarak bot testnette çalışacak
WALLET = TESTNET_WALLET
Kayıt ve bot ayarı
logging.basicConfig(level=logging.INFO)
bot = Bot(token=BOT_TOKEN, parse_mode=types.ParseMode.HTML)
dp = Dispatcher(bot, storage=MemoryStorage())
Durumlar
Durumları, bot iş akışını aşamalara ayırmak için kullanabiliriz. Her aşamanın belirli bir görev için özel olmasını sağlayabiliriz.
class DataInput (StatesGroup):
firstState = State()
secondState = State()
WalletState = State()
PayState = State()
Detaylar ve örnekler için Aiogram belgelerine bakabilirsiniz.
Mesaj işleyicileri
Bu bölümde bot etkileşim mantığını yazacağız.
İki tür işleyici kullanacağız:
message_handler
, kullanıcıdan gelen mesajları işlemek için kullanılır.callback_query_handler
, inline klavyeden yapılan geri çağrıları işlemek için kullanılır.
Kullanıcıdan gelen bir mesajı işlemek istiyorsak, message_handler
kullanacağız ve fonksiyonun üzerine @dp.message_handler
dekoratörünü yerleştireceğiz. Bu durumda, kullanıcı botun bir mesaj göndermesi durumunda fonksiyon çağrılacaktır.
Dekoratörde, fonksiyonun çağrılacağı koşulları belirtebiliriz. Örneğin, fonksiyonun yalnızca kullanıcının /start
metniyle bir mesaj göndermesi durumunda çağrılmasını istiyorsak, şu şekilde yazmalıyız:
@dp.message_handler(commands=['start'])
İşleyicilere bir asenkron fonksiyona atanması gerekir. Bu durumda async def
sözdizimini kullanacağız. async def
söz dizimi, asenkron bir şekilde çağrılacak fonksiyonu tanımlamak için kullanılır.
/start
/start
komutunu başlatan handler ile başlayalım.
@dp.message_handler(commands=['start'], state='*')
async def cmd_start(message: types.Message):
await message.answer(f"WORKMODE: {WORK_MODE}")
# kullanıcı veritabanında mı? değilse ekle
isOld = db.check_user(message.from_user.id, message.from_user.username, message.from_user.first_name)
:::tip
Kullanıcı veritabanında mevcutsa, ona farklı bir şekilde hitap edebiliriz.
:::
if isOld == False:
await message.answer(f"Burada yenisin, {message.from_user.first_name}!")
await message.answer(f"Hava almak için /buy gönder")
else:
await message.answer(f"Bir kez daha hoş geldin, {message.from_user.first_name}!")
await message.answer(f"Daha fazla hava almak için /buy gönder")
await DataInput.firstState.set()
Bu handler'ın dekoratöründe state='*'
görüyoruz. Bu, bu handler'ın botun durumuna bakılmaksızın çağrılacağı anlamına gelir. Handler'ın yalnızca bot belirli bir durumda olduğunda çağrılmasını istiyorsak, state=DataInput.firstState
yazmamız gerekir. Bu durumda, handler yalnızca bot firstState
durumundayken çağrılacaktır.
Kullanıcı /start
komutunu gönderdikten sonra, bot db.check_user
fonksiyonu kullanarak kullanıcının veritabanında olup olmadığını kontrol eder. Eğer yoksa, onu ekleyecektir. Bu fonksiyon ayrıca bir bool değer de döndürür ve bunu kullanıcıya farklı bir şekilde hitap etmek için kullanabiliriz. Daha sonra bot durumu firstState
olarak ayarlayacaktır.
/cancel
Sonraki /cancel
komutunu yöneten handler. Bu, firstState
durumuna geri dönmek için gereklidir.
@dp.message_handler(commands=['cancel'], state="*")
async def cmd_cancel(message: types.Message):
await message.answer("İptal edildi")
await message.answer("/start ile yeniden başla")
await DataInput.firstState.set()
/buy
Ve elbette /buy
komutunu yöneten handler. Bu örnekte farklı hava türlerini satacağız. Hava türünü seçmek için cevap tuş takımını kullanacağız.
# /buy komutunu yöneten handler
@dp.message_handler(commands=['buy'], state=DataInput.firstState)
async def cmd_buy(message: types.Message):
# hava türleri ile cevap tuş takımı
keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True)
keyboard.add(types.KeyboardButton('Tamamen saf 🔥'))
keyboard.add(types.KeyboardButton('Bahar ormanı 🌲'))
keyboard.add(types.KeyboardButton('Deniz esintisi 🌊'))
keyboard.add(types.KeyboardButton('Taze asfalt 🛣'))
await message.answer(f"Seçtiğin hava: (veya /cancel)", reply_markup=keyboard)
await DataInput.secondState.set()
Yani, kullanıcı /buy
komutunu gönderdiğinde, bot ona hava türleriyle bir cevap tuş takımı gönderir. Kullanıcı hava türünü seçtikten sonra, bot durumu secondState
olarak ayarlayacaktır.
Bu handler, yalnızca secondState
ayarlandığında çalışır ve kullanıcının hava türü ile bir mesaj bekler. Bu durumda, kullanıcının seçtiği hava türünü saklamamız gerekiyor; bu yüzden fonksiyona FSMContext'i argüman olarak geçiriyoruz.
FSMContext, botun belleğinde veri saklamak için kullanılır. İçinde herhangi bir veriyi saklayabiliriz ama bu bellek kalıcı değildir, bu yüzden bot yeniden başlatıldığında veriler kaybolur. Ama geçici verileri saklamak için kullanmak iyidir.
# hava türünü yönet
@dp.message_handler(state=DataInput.secondState)
async def air_type(message: types.Message, state: FSMContext):
if message.text == "Tamamen saf 🔥":
await state.update_data(air_type="Tamamen saf 🔥")
elif message.text == "Taze asfalt 🛣":
await state.update_data(air_type="Taze asfalt 🛣")
elif message.text == "Bahar ormanı 🌲":
await state.update_data(air_type="Bahar ormanı 🌲")
elif message.text == "Deniz esintisi 🌊":
await state.update_data(air_type="Deniz esintisi 🌊")
else:
await message.answer("Yanlış hava türü")
await DataInput.secondState.set()
return
await DataInput.WalletState.set()
await message.answer(f"Cüzdan adresini gönder")
Kullan...
await state.update_data(air_type="Tamamen saf 🔥")
... hava türünü FSMContext'te saklamak için. Daha sonra, durumu WalletState
olarak ayarlıyoruz ve kullanıcıdan cüzdan adresini göndermesini istiyoruz.
Bu handler, yalnızca WalletState
ayarlandığında çalışacak ve kullanıcının cüzdan adresiyle bir mesaj bekleyecektir.
Sonraki handler oldukça karmaşık görünüyor ama öyle değil. Öncelikle, mesajın geçerli bir cüzdan adresi olup olmadığını kontrol ediyoruz; len(message.text) == 48
çünkü cüzdan adresi 48 karakter uzunluğundadır. Daha sonra, adresin geçerli olup olmadığını kontrol etmek için api.detect_address
fonksiyonunu kullanıyoruz. API bölümünden bildiğiniz gibi, bu fonksiyon aynı zamanda veritabanında saklanacak "Doğru" adresi döndürür.
Geçerli bir adres olmasına dikkat edin, aksi halde işlem başarısız olabilir.
Daha sonra, FSMContext'ten hava türünü alıyoruz ve bunu user_data
değişkenine kaydediyoruz.
Artık ödeme işlemi için gereken tüm verilere sahibiz. Tek yapmamız gereken bir ödeme bağlantısı oluşturmak ve bunu kullanıcıya göndermek. Hadi inline tuş takımını kullanalım.
Bu örnekte ödeme için üç tuş oluşturulacaktır:
- resmi TON Cüzdanı için
- Tonhub için
- Tonkeeper için
Cüzdanlar için özel tuşların avantajı, eğer kullanıcı henüz bir cüzdana sahip değilse, sitenin ona bir tane kurması için yardım etmesidir.
İstediğinizi kullanmakta özgürsünüz.
Ve işlemin başarılı olup olmadığını kontrol edebilmemiz için kullanıcının işlemden sonra basacağı bir tuşa ihtiyacımız var.
@dp.message_handler(state=DataInput.WalletState)
async def user_wallet(message: types.Message, state: FSMContext):
if len(message.text) == 48:
res = api.detect_address(message.text)
if res == False:
await message.answer("Yanlış cüzdan adresi")
await DataInput.WalletState.set()
return
else:
user_data = await state.get_data()
air_type = user_data['air_type']
# inline buton "işlemi kontrol et"
keyboard2 = types.InlineKeyboardMarkup(row_width=1)
keyboard2.add(types.InlineKeyboardButton(text="İşlemi kontrol et", callback_data="check"))
keyboard1 = types.InlineKeyboardMarkup(row_width=1)
keyboard1.add(types.InlineKeyboardButton(text="Ton Cüzdanı", url=f"ton://transfer/{WALLET}?amount=1000000000&text={air_type}"))
keyboard1.add(types.InlineKeyboardButton(text="Tonkeeper", url=f"https://app.tonkeeper.com/transfer/{WALLET}?amount=1000000000&text={air_type}"))
keyboard1.add(types.InlineKeyboardButton(text="Tonhub", url=f"https://tonhub.com/transfer/{WALLET}?amount=1000000000&text={air_type}"))
await message.answer(f"{air_type} seçtiniz")
await message.answer(f"<code>1</code> toncoin'i adresine gönder \n<code>{WALLET}</code> \nşu yorum ile \n<code>{air_type}</code> \nsenin cüzdanından ({message.text})", reply_markup=keyboard1)
await message.answer(f"Ödeme sonrası butona tıklayın", reply_markup=keyboard2)
await DataInput.PayState.set()
await state.update_data(wallet=res)
await state.update_data(value_nano="1000000000")
else:
await message.answer("Yanlış cüzdan adresi")
await DataInput.WalletState.set()
/me
Gerekli son bir mesaj yöneticisi /me
komutu içindir. Bu, kullanıcının ödemelerini gösterir.
# /me komutunu yöneten handler
@dp.message_handler(commands=['me'], state="*")
async def cmd_me(message: types.Message):
await message.answer(f"İşlemleriniz")
# db.get_user_payments kullanıcı için işlemler listesini döndürür
transactions = db.get_user_payments(message.from_user.id)
if transactions == False:
await message.answer(f"Hiç işlemin yok")
else:
for transaction in transactions:
# blockchain değerleri nanotonda saklar. 1 toncoin = 1000000000 blockchain'de
await message.answer(f"{int(transaction['value'])/1000000000} - {transaction['comment']}")
Callback Yöneticileri
Butonlarda callback verilerini ayarlayabiliriz; bu veriler, kullanıcı butona bastığında bot'a gönderilecektir. Kullanıcının işlemden sonra basacağı butonda, callback verilerini "check" olarak ayarlıyoruz. Sonuç olarak, bu callback'i yönetmemiz gerekiyor.
Callback yöneticileri, mesaj yöneticilerine oldukça benzer ama message
yerine types.CallbackQuery
argümanı alırlar. Fonksiyon dekoratörü de farklıdır.
@dp.callback_query_handler(lambda call: call.data == "check", state=DataInput.PayState)
async def check_transaction(call: types.CallbackQuery, state: FSMContext):
# bildirim gönder
user_data = await state.get_data()
source = user_data['wallet']
value = user_data['value_nano']
comment = user_data['air_type']
result = api.find_transaction(source, value, comment)
if result == False:
await call.answer("Biraz bekle, 10 saniye içinde tekrar dene. İşlemin durumunu explorer'dan (tonscan.org/) de kontrol edebilirsin.", show_alert=True)
else:
db.v_wallet(call.from_user.id, source)
await call.message.edit_text("İşlem onaylandı \n/start ile yeniden başla")
await state.finish()
await DataInput.firstState.set()
Bu handler'da FSMContext'ten kullanıcı verilerini alıyoruz ve işlemin başarılı olup olmadığını kontrol etmek için api.find_transaction
fonksiyonunu kullanıyoruz. Başarılı ise, cüzdan adresini veritabanında saklıyoruz ve kullanıcıya bir bildirim gönderiyoruz. Daha sonra kullanıcı, /me
komutu ile işlemlerini bulabilir.
main.py'nin Son Kısmı
Son olarak, unutmayın:
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
Bu kısım botu başlatmak için gereklidir. skip_updates=True
ile eski mesajları işlemek istemediğimizi belirtiyoruz. Ama tüm mesajları işlemek isterseniz, bunu False
olarak ayarlayabilirsiniz.
main.py
dosyasının tüm kodu burada bulunabilir.
Botu Çalıştırma
Sonunda başardık! Artık çalışır bir botunuz olmalı. Bunu test edebilirsiniz!
Botu çalıştırma adımları:
config.json
dosyasını doldurun.main.py
dosyasını çalıştırın.
Tüm dosyaların aynı klasörde olması gerekmektedir. Botu başlatmak için main.py
dosyasını çalıştırmanız gerekiyor. Bunu IDE'nizde veya terminalde şu şekilde yapabilirsiniz:
python main.py
Herhangi bir hata alırsanız, bunları terminalde kontrol edebilirsiniz. Belki kodda bir şeyi atladınız.
Çalışan bir bot örneği @AirDealerBot
Referanslar
- TON için ton-footsteps/8 parçası olarak yapıldı
- Lev (Telegram @Revuza, LevZed GitHub'da)