跳到主要内容

使用 TON 的商店机器人

在这篇文章中,我们将引导你完成在 Telegram 机器人中接受付款的过程。

📖 你将学到什么

在这篇文章中,你将学习如何:

  • 使用 Python + Aiogram 创建一个 Telegram 机器人
  • 使用公开的 TON API(TON Center)
  • 使用 SQlite 数据库

最后:通过前面步骤的知识,在 Telegram 机器人中接受付款。

📚 在我们开始之前

确保你已经安装了最新版本的 Python,并且已经安装了以下包:

  • aiogram
  • requests
  • sqlite3

🚀 我们开始吧!

我们将按照以下顺序操作:

  1. 使用 SQlite 数据库
  2. 使用公开的 TON API(TON Center)
  3. 使用 Python + Aiogram 创建一个 Telegram 机器人
  4. 盈利!

让我们在项目目录中创建以下四个文件:

telegram-bot
├── config.json
├── main.py
├── api.py
└── db.py

配置

config.json 中,我们将存储我们的机器人令牌和我们的公开 TON API 密钥。

{
"BOT_TOKEN": "Your bot token",
"MAINNET_API_TOKEN": "Your mainnet api token",
"TESTNET_API_TOKEN": "Your testnet api token",
"MAINNET_WALLET": "Your mainnet wallet",
"TESTNET_WALLET": "Your testnet wallet",
"WORK_MODE": "testnet"
}

config.json 中,我们决定我们将使用哪个网络:testnetmainnet

数据库

创建数据库

这个示例使用本地 Sqlite 数据库。

创建 db.py

开始使用数据库,我们需要导入 sqlite3 模块和一些用于处理时间的模块。

import sqlite3
import datetime
import pytz
  • sqlite3—用于操作 sqlite 数据库的模块
  • datetime—用于处理时间的模块
  • pytz—用于处理时区的模块

接下来,我们需要创建一个数据库的连接和一个用于操作它的游标:

locCon = sqlite3.connect('local.db', check_same_thread=False)
cur = locCon.cursor()

如果数据库不存在,将会自动创建。

现在我们可以创建表格了。我们有两个表格。

交易:

CREATE TABLE transactions (
source VARCHAR (48) NOT NULL,
hash VARCHAR (50) UNIQUE
NOT NULL,
value INTEGER NOT NULL,
comment VARCHAR (50)
);
  • source—付款人的钱包地址
  • hash—交易哈希
  • value—交易价值
  • comment—交易备注

用户:

CREATE TABLE users (
id INTEGER UNIQUE
NOT NULL,
username VARCHAR (33),
first_name VARCHAR (300),
wallet VARCHAR (50) DEFAULT none
);
  • id—Telegram 用户 ID
  • username—Telegram 用户名
  • first_name—Telegram 用户的名字
  • wallet—用户钱包地址

users 表中,我们存储用户 :) 他们的 Telegram ID、@username、 名字和钱包。第一次成功付款时,钱包将被添加到数据库中。

transactions 表存储已验证的交易。 要验证交易,我们需要哈希、来源、值和备注。

要创建这些表格,我们需要运行以下函数:

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()

如果这些表格还没有被创建,这段代码将会创建它们。

使用数据库

让我们分析一种情况: 用户进行了一笔交易。我们如何验证它?我们如何确保同一笔交易不被二次确认?

交易中有一个 body_hash,通过它我们可以轻松地了解数据库中是否存在该交易。

我们只添加我们确定的交易到数据库。check_transaction 函数检查数据库中是否存在找到的交易。

add_v_transaction 将交易添加到交易表。

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 检查用户是否在数据库中,并且如果不在,则添加他。

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

用户可以在表中存储一个钱包。它是在第一次成功购买时添加的。v_wallet 函数检查用户是否有关联的钱包。如果有,则返回它。如果没有,则添加。

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 简单地返回用户的钱包。

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 返回用户的支付列表。 这个函数检查用户是否有钱包。如果有,则返回支付列表。

def get_user_payments(user_id):
wallet = get_user_wallet(user_id)

if wallet == "none":
return "You have no wallet"
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

我们有能力使用一些网络成员提供的第三方 API 与区块链进行交互。通过这些服务,开发者可以跳过运行自己的节点和自定义 API 的步骤。

需要的请求

实际上,我们需要确认用户已经向我们转账了所需金额吗?

我们只需要查看我们钱包的最新进账转账,并在其中找到一笔来自正确地址、正确金额的交易(可能还有一个独特的备注)。 为了所有这一切,TON Center 有一个 getTransactions 方法。

getTransactions

默认情况下,如果我们使用它,我们将获得最后 10 条交易。然而,我们也可以表示我们需要更多,但这会略微增加响应时间。而且,很有可能,你不需要那么多。

如果您想要更多,那么每笔交易都有 lthash。您可以查看例如 30 条交易,如果没在其中找到正确的一笔,那么取最后一笔的 lthash 添加到请求中。

这样您就可以得到下一个 30 条交易,以此类推。

例如,测试网络中有一个钱包 EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5,它有一些交易:

使用查询 我们将得到包含两笔交易的响应(现在不需要的一些信息已经被隐藏,完整答案可以在上面的链接中看到)。

{
"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": "Sea breeze 🌊"
},
"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": "Spring forest 🌲"
},
"out_msgs": []
}
]
}

我们从这个地址收到了最后两笔交易。当添加 lthash 到查询中时,我们将再次收到两笔交易。然而,第二笔将成为下一笔连续的交易。也就是说,我们将获得这个地址的第二笔和第三笔交易。

{
"ok": true,
"result": [
{
"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": "Spring forest 🌲"
},
"out_msgs": []
},
{
"transaction_id": {
"lt": "1845458000003",
"hash": "k5U9AwIRNGhC10hHJ3MBOPT//bxAgW5d9flFiwr1Sao="
},
"in_msg": {
"source": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"destination": "EQAVKMzqtrvNB2SkcBONOijadqFZ1gMdjmzh1Y3HB1p_zai5",
"value": "1000000000",
"body_hash": "XpTXquHXP64qN6ihHe7Tokkpy88tiL+5DeqIrvrNCyo=",
"message": "Second"
},
"out_msgs": []
}
]
}

请求将看起来像这样。

我们还需要一个方法 detectAddress

这是测试网上的 Tonkeeper 钱包地址的一个例子:kQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aCTb。如果我们在浏览器中查找交易,代替上述地址,有:EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R

这个方法返回给我们“正确”的地址。

{
"ok": true,
"result": {
"raw_form": "0:b3409241010f85ac415cbf13b9b0dc6157d09a39d2bd0827eadb20819f067868",
"bounceable": {
"b64": "EQCzQJJBAQ+FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R",
"b64url": "EQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aJ9R"
},
"non_bounceable": {
"b64": "UQCzQJJBAQ+FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aMKU",
"b64url": "UQCzQJJBAQ-FrEFcvxO5sNxhV9CaOdK9CCfq2yCBnwZ4aMKU"
}
}
}

我们需要 b64url

这个方法让我们能够验证用户的地址。

大部分而言,这就是我们所需要的。

API 请求及其处理方法

让我们回到 IDE。创建文件 api.py

导入所需的库。

import requests
import json
# We import our db module, as it will be convenient to add from here
# transactions to the database
import db
  • requests—用来向 API 发送请求
  • json—用来处理 json
  • db—用来处理我们的 sqlite 数据库

让我们创建两个变量来存储请求的开头。

# This is the beginning of our requests
MAINNET_API_BASE = "https://toncenter.com/api/v2/"
TESTNET_API_BASE = "https://testnet.toncenter.com/api/v2/"

从 config.json 文件中获取所有 API 令牌和钱包。

# Find out which network we are working on
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']

根据网络,我们取所需的数据。

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

我们的第一个请求函数 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

在输入中,我们有预计的地址,输出要么是我们需要的“正确”地址,以便进行进一步的工作,要么是 False。

你可能会注意到请求末尾出现了 API 密钥。它是为了移除对 API 请求数量的限制。没有它,我们被限制为每秒一个请求。

这里是 getTransactions 的下一个函数:

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']

此函数返回最后 30 次对我们 WALLET 的交易。

这里可以看到 archival=true。这是因为我们只需要从具有完整区块链历史记录的节点获取交易。

在输出中,我们获得一个交易列表—[0,1,…,29]。简而言之,是字典列表。

最后一个函数:

def find_transaction(user_wallet, value, comment):
# Get the last 30 transactions
transactions = get_address_transactions()
for transaction in transactions:
# Select the incoming "message" - transaction
msg = transaction['in_msg']
if msg['source'] == user_wallet and msg['value'] == value and msg['message'] == comment:
# If all the data match, we check that this transaction
# we have not verified before
t = db.check_transaction(msg['body_hash'])
if t == False:
# If not, we write in the table to the verified
# and return True
db.add_v_transaction(
msg['source'], msg['body_hash'], msg['value'], msg['message'])
print("find transaction")
print(
f"transaction from: {msg['source']} \nValue: {msg['value']} \nComment: {msg['message']}")
return True
# If this transaction is already verified, we check the rest, we can find the right one
else:
pass
# If the last 30 transactions do not contain the required one, return False
# Here you can add code to see the next 29 transactions
# However, within the scope of the Example, this would be redundant.
return False

输入是“正确”的钱包地址、金额和评论。如果找到预期的进账交易,输出为 True;否则为 False。

Telegram 机器人

首先,让我们为机器人创建基础。

导入

在这部分,我们将导入所需的库。

来自 aiogram,我们需要 BotDispatchertypesexecutor

from aiogram import Bot, Dispatcher, executor, types

MemoryStorage 是用于临时存储信息的。

FSMContext, State, 和 StatesGroup 用于与状态机工作。

from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters.state import State, StatesGroup

json 用来处理 json 文件。logging 用来记录错误。

import json
import logging

apidb 是我们自己的文件,稍后我们将填充内容。

import db
import api

配置设置

建议您将如 BOT_TOKEN 和接收付款的钱包等数据存储在一个名为 config.json 的单独文件中,以便于使用。

{
"BOT_TOKEN": "Your bot token",
"MAINNET_API_TOKEN": "Your mainnet api token",
"TESTNET_API_TOKEN": "Your testnet api token",
"MAINNET_WALLET": "Your mainnet wallet",
"TESTNET_WALLET": "Your testnet wallet",
"WORK_MODE": "testnet"
}

机器人令牌

BOT_TOKEN 是你的 Telegram 机器人令牌,来自 @BotFather

工作模式

WORK_MODE 键中,我们将定义机器人的工作模式—在测试网或主网;分别为 testnetmainnet

API 令牌

*_API_TOKEN 的 API 令牌可以在 TON Center 机器人处获取:

将配置连接到我们的机器人

接下来,我们完成机器人设置。

config.json 获取机器人工作所需的令牌:

with open('config.json', 'r') as f:
config_json = json.load(f)
BOT_TOKEN = config_json['BOT_TOKEN']
# put wallets here to receive payments
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:
# By default, the bot will run on the testnet
WALLET = TESTNET_WALLET

日志记录和机器人设置

logging.basicConfig(level=logging.INFO)
bot = Bot(token=BOT_TOKEN, parse_mode=types.ParseMode.HTML)
dp = Dispatcher(bot, storage=MemoryStorage())

状态

我们需要使用状态将机器人工作流程划分为阶段。我们可以将每个阶段专门用于特定任务。

class DataInput (StatesGroup):
firstState = State()
secondState = State()
WalletState = State()
PayState = State()

详情和示例请参见 Aiogram 文档

消息处理器(Message handlers)

这是我们将编写机器人交互逻辑的部分。

我们将使用两种类型的处理器:

  • message_handler 用于处理用户消息。
  • callback_query_handler 用于处理来自内联键盘的回调。

如果我们想处理用户的消息,我们将使用 message_handler 并在函数上方放置 @dp.message_handler 装饰器。在这种情况下,当用户向机器人发送消息时,将调用该函数。

在装饰器中,我们可以指定将在何种条件下调用该函数。例如,如果我们想要在用户发送文本 /start 的消息时调用函数,那么我们将编写以下内容:

@dp.message_handler(commands=['start'])

处理器需要分配给一个异步函数。在这种情况下,我们将使用 async def 语法。async def 语法用于定义将异步调用的函数。

/start

让我们从 /start 命令处理器开始。

@dp.message_handler(commands=['start'], state='*')
async def cmd_start(message: types.Message):
await message.answer(f"WORKMODE: {WORK_MODE}")
# check if user is in database. if not, add him
isOld = db.check_user(
message.from_user.id, message.from_user.username, message.from_user.first_name)
# if user already in database, we can address him differently
if isOld == False:
await message.answer(f"You are new here, {message.from_user.first_name}!")
await message.answer(f"to buy air send /buy")
else:
await message.answer(f"Welcome once again, {message.from_user.first_name}!")
await message.answer(f"to buy more air send /buy")
await DataInput.firstState.set()

在此处理器的装饰器中,我们看到 state='*'。这意味着无论机器人的状态如何,该处理器都将被调用。如果我们希望处理器仅在机器人处于特定状态时调用,我们将编写 state=DataInput.firstState。在这种情况下,处理器仅在机器人处于 firstState 状态时被调用。

用户发送 /start 命令后,机器人将使用 db.check_user 函数检查用户是否在数据库中。如果不是,它将添加他。此函数还将返回布尔值,我们可以使用它以不同的方式对待用户。之后,机器人将设置状态为 firstState

/cancel

接下来是 /cancel 命令处理器。它需要返回到 firstState 状态。

@dp.message_handler(commands=['cancel'], state="*")
async def cmd_cancel(message: types.Message):
await message.answer("Canceled")
await message.answer("/start to restart")
await DataInput.firstState.set()

/buy

当然还有 /buy 命令处理器。在这个示例中我们将出售不同类型的空气。我们将使用reply keyboard来选择air types。

# /buy command handler
@dp.message_handler(commands=['buy'], state=DataInput.firstState)
async def cmd_buy(message: types.Message):
# reply keyboard with air types
keyboard = types.ReplyKeyboardMarkup(
resize_keyboard=True, one_time_keyboard=True)
keyboard.add(types.KeyboardButton('Just pure 🌫'))
keyboard.add(types.KeyboardButton('Spring forest 🌲'))
keyboard.add(types.KeyboardButton('Sea breeze 🌊'))
keyboard.add(types.KeyboardButton('Fresh asphalt 🛣'))
await message.answer(f"Choose your air: (or /cancel)", reply_markup=keyboard)
await DataInput.secondState.set()

所以,当用户发送 /buy 命令时,机器人发送一个reply keyboard给他,上面有air type。用户选择air type后,机器人将设置状态为 secondState

此处理器将仅在 secondState 被设置时工作,并将等待用户发送air type的消息。在这种情况下,我们需要存储用户选择的air type,因此我们将 FSMContext 作为参数传递给函数。

FSMContext 用于在机器人的内存中存储数据。我们可以在其中存储任何数据,但这个内存不是持久的,所以如果机器人重启,数据将会丢失。但它很适合存储临时数据。

# handle air type
@dp.message_handler(state=DataInput.secondState)
async def air_type(message: types.Message, state: FSMContext):
if message.text == "Just pure 🌫":
await state.update_data(air_type="Just pure 🌫")
elif message.text == "Fresh asphalt 🛣":
await state.update_data(air_type="Fresh asphalt 🛣")
elif message.text == "Spring forest 🌲":
await state.update_data(air_type="Spring forest 🌲")
elif message.text == "Sea breeze 🌊":
await state.update_data(air_type="Sea breeze 🌊")
else:
await message.answer("Wrong air type")
await DataInput.secondState.set()
return
await DataInput.WalletState.set()
await message.answer(f"Send your wallet address")

使用...

await state.update_data(air_type="Just pure 🌫")

...在 FSMContext 中存储air type之后,我们设置状态为 WalletState 并要求用户发送他的钱包地址。

此处理器将仅在 WalletState 被设置时工作,并将等待用户发送钱包地址的消息。

下一个处理器看起来可能非常复杂,但实际上并不难。首先,我们使用 len(message.text) == 48 检查消息是否是有效的钱包地址,因为钱包地址长 48 个字符。之后,我们使用 api.detect_address 函数检查地址是否有效。如你从 API 部分记得的那样,这个函数还返回 "正确" 地址,它将被存储在数据库中。

之后,我们使用 await state.get_data() 从 FSMContext 获取air type并将其存储在 user_data 变量中。

现在我们有了付款过程所需的所有数据。我们只需要生成一个付款链接并发送给用户。让我们使用inline keyboard。

在此示例中,将为付款创建三个按钮:

  • 官方 TON Wallet
  • Tonhub
  • Tonkeeper

对于钱包的特殊按钮的优点是,如果用户尚未拥有钱包,则网站将提示他安装一个。

你可以随意使用你想要的内容。

我们还需要一个用户付款后按下的按钮,这样我们就可以检查支付是否成功。

@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("Wrong wallet address")
await DataInput.WalletState.set()
return
else:
user_data = await state.get_data()
air_type = user_data['air_type']
# inline button "check transaction"
keyboard2 = types.InlineKeyboardMarkup(row_width=1)
keyboard2.add(types.InlineKeyboardButton(
text="Check transaction", callback_data="check"))
keyboard1 = types.InlineKeyboardMarkup(row_width=1)
keyboard1.add(types.InlineKeyboardButton(
text="Ton Wallet", 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"You choose {air_type}")
await message.answer(f"Send <code>1</code> toncoin to address \n<code>{WALLET}</code> \nwith comment \n<code>{air_type}</code> \nfrom your wallet ({message.text})", reply_markup=keyboard1)
await message.answer(f"Click the button after payment", reply_markup=keyboard2)
await DataInput.PayState.set()
await state.update_data(wallet=res)
await state.update_data(value_nano="1000000000")
else:
await message.answer("Wrong wallet address")
await DataInput.WalletState.set()

/me

我们需要的最后一个消息处理器是 /me 命令。它显示用户的支付信息。

# /me command handler
@dp.message_handler(commands=['me'], state="*")
async def cmd_me(message: types.Message):
await message.answer(f"Your transactions")
# db.get_user_payments returns list of transactions for user
transactions = db.get_user_payments(message.from_user.id)
if transactions == False:
await message.answer(f"You have no transactions")
else:
for transaction in transactions:
# we need to remember that blockchain stores value in nanotons. 1 toncoin = 1000000000 in blockchain
await message.answer(f"{int(transaction['value'])/1000000000} - {transaction['comment']}")

回调处理器(Callback handlers)

我们可以在按钮中设置回调数据,当用户按下按钮时,这些数据将被发送给机器人。在用户交易后按下的按钮中,我们设置回调数据为 "check"。因此,我们需要处理这个回调。

回调处理器与消息处理器非常相似,但它们有 types.CallbackQuery 作为参数,而不是 message。函数装饰器也有所不同。

@dp.callback_query_handler(lambda call: call.data == "check", state=DataInput.PayState)
async def check_transaction(call: types.CallbackQuery, state: FSMContext):
# send notification
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("Wait a bit, try again in 10 seconds. You can also check the status of the transaction through the explorer (tonscan.org/)", show_alert=True)
else:
db.v_wallet(call.from_user.id, source)
await call.message.edit_text("Transaction is confirmed \n/start to restart")
await state.finish()
await DataInput.firstState.set()

在此处理器中,我们从 FSMContext 获取用户数据并使用 api.find_transaction 函数检查交易是否成功。如果成功,我们将钱包地址存储在数据库中,并向用户发送通知。此后,用户可以使用 /me 命令查找他的交易。

main.py 的最后一部分

最后,别忘了:

if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)

这部分需要启动机器人。 在 skip_updates=True 中,我们指定我们不想处理旧消息。但如果您想处理所有消息,可以将其设置为 False

信息

main.py 的所有代码可以在这里找到。

机器人动起来

我们终于做到了!现在你应该有一个工作中的机器人。你可以测试它!

运行机器人的步骤:

  1. 填写 config.json 文件。
  2. 运行 main.py

所有文件必须在同一个文件夹中。要启动机器人,您需要运行 main.py 文件。您可以在 IDE 或终端中这样做:

python main.py

如果您遇到任何错误,可以在终端中检查。也许您在代码中漏掉了一些东西。

工作中的机器人示例@AirDealerBot

bot

参考资料