如何用node-telegram-bot-api实现Telegram Bot的CA文本一键复制带通知
我正在用Node.js的node-telegram-bot-api开发一款Telegram机器人,用来推送Solana区块链的交易通知。每条交易消息里包含合约地址(CA),我希望CA文本支持点击操作:用户点击后自动复制到剪贴板,同时Telegram顶部显示“已复制到剪贴板”的系统通知。
期望效果
我见过其他机器人实现了这个功能:消息中的CA(比如Ek4FUaAyinUT5whKyUMFv9qWpggud32P)可点击,点击后自动复制CA并弹出“CA已复制到剪贴板”的Telegram通知,我想在自己的机器人里复刻这个功能。
当前实现方案
目前我使用parse_mode: 'HTML',把CA用反引号包裹(比如Ek4FUaAyinUT5whKyUMFv9qWpggud32P)发送消息,还加了“(长按复制)”的提示。用户可以长按CA复制,触发Telegram的“已复制到剪贴板”通知,但我希望改成点击即可复制,不用长按。
以下是bot.js中构建和发送交易消息的相关代码:
const subscribeTrading = async (walletAddress, walletName, chatId) => { // ... (其他交易处理代码) let message = `<a href="https://solscan.io/account/${walletAddress}">${walletName}</a>\n\n`; if (['BUY', 'SELL'].includes(transactionType)) { if (Math.abs(solAmount) < 0.002) return; message += `${transactionType === 'BUY' ? '🟢' : '🔴'} ${transactionType === 'BUY' ? 'BOUGHT' : 'SOLD'} ${Math.abs(solAmount).toFixed(3)} SOL | ${formatTokenAmount(tokenAmount)} Tokens\n\n` + `🚀 <b>Token:</b> ${tokenName} (${tokenSymbol})${pumpFunLink}\n\n` + `📜 <b>CA:</b> \`${tokenAddress}\` (long-press to copy)`; } else if (['SENT_TOKEN', 'RECEIVED_TOKEN'].includes(transactionType)) { message += `📤 ${transactionType === 'SENT_TOKEN' ? 'SENT' : 'RECEIVED'} | ${formatTokenAmount(tokenAmount)} Tokens\n\n` + `🚀 <b>Token:</b> ${tokenName} (${tokenSymbol})${pumpFunLink}\n\n` + `📜 <b>CA:</b> \`${tokenAddress}\` (long-press to copy)`; } else { if (Math.abs(solTransferAmount) < 0.002) return; message += `📤 ${transactionType === 'SENT' ? 'SENT' : 'RECEIVED'} ${solTransferAmount.toFixed(3)} SOL`; } const timestamp = new Date().toLocaleTimeString('en-GB', { hour12: false }); message += `\n\n🔗 <a href="https://solscan.io/tx/${signature}">Solscan</a> | ⏰ ${timestamp}`; const sub = subscriptions.get(walletAddress); if (sub) { for (const cid of sub.chatIds) { if (['BUY', 'SELL', 'SENT_TOKEN', 'RECEIVED_TOKEN'].includes(transactionType)) { try { await sendTelegramMessage(cid, message, tokenName, tokenAddress); } catch (sendError) { console.log(`Failed to send Telegram message to ${cid}:`, sendError.message); await bot.sendMessage(cid, message, { parse_mode: 'HTML', disable_web_page_preview: true }); } } else { await sendTelegramMessage(cid, message); } } } }; const sendTelegramMessage = async (chatId, message, tokenName = null, tokenCA = null) => { try { const user = await new Promise((resolve, reject) => { db.get('SELECT settings FROM users WHERE chatId = ?', [chatId], (err, row) => { if (err) reject(err); resolve(row ? JSON.parse(row.settings) : { chartViewer: 'DEX Screener', buyBot: 'Trojan' }); }); }); const options = { parse_mode: 'HTML', disable_web_page_preview: true }; if (tokenName && tokenCA) { const chartLabel = user.chartViewer; const buyBotLabel = user.buyBot; const chartUrl = getChartUrl(tokenCA, user.chartViewer); const buttons = []; try { new URL(chartUrl); buttons.push({ text: `📊 ${chartLabel}`, url: chartUrl }); } catch (urlError) { console.log(`Invalid chart URL for ${tokenCA}: ${chartUrl}`, urlError.message); } const botUsernames = { 'Trojan': 'nestor_trojanbot', 'GMGN': 'GMGN_sol03_bot', 'MEVX': 'MevxTradingBot', 'Bonk': 'bonkbot_bot', }; if (buyBotLabel === 'BullX NEO') { const buyUrl = `https://neo.bullx.io/${encodeURIComponent(tokenCA)}`; try { new URL(buyUrl); buttons.push({ text: `💸 ${buyBotLabel}`, url: buyUrl }); } catch (urlError) { console.log(`Invalid buy URL for ${tokenCA}: ${buyUrl}`, urlError.message); } } else if (botUsernames[buyBotLabel]) { let deepLink = `https://t.me/${botUsernames[buyBotLabel]}?start=${encodeURIComponent(tokenCA)}`; try { new URL(deepLink); buttons.push({ text: `💸 ${buyBotLabel}`, url: deepLink }); } catch (urlError) { console.log(`Invalid deep link for ${buyBotLabel}: ${deepLink}`, urlError.message); message += `\n\nIf the ${buyBotLabel} button fails, open @${botUsernames[buyBotLabel]} and send /start ${tokenCA}`; } } if (buttons.length > 0) { options.reply_markup = { inline_keyboard: [buttons] }; } } await bot.sendMessage(chatId, message, options); } catch (error) { console.log(`Error sending message to ${chatId}:`, error.message); } };
已尝试的方案
使用Markdown格式:设置
parse_mode: 'MARKDOWN',将CA用反引号包裹,示例代码:bot.sendMessage(chatId, "Hi. `Press me!`", { parse_mode: 'MARKDOWN' });这种方式让CA显示为等宽字体,但还是需要长按复制,无法实现点击复制。
使用Inline键盘按钮:添加带
callback_data的按钮,在回调中返回提示,示例代码:options.reply_markup = { inline_keyboard: [[{ text: `📜 CA: ${tokenCA}`, callback_data: `copy_ca_${tokenCA}` }]] }; // 回调处理 bot.on('callback_query', async (callbackQuery) => { const chatId = callbackQuery.message.chat.id.toString(); const data = callbackQuery.data; if (data.startsWith('copy_ca_')) { const tokenCA = data.replace('copy_ca_', ''); await bot.answerCallbackQuery(callbackQuery.id, { text: `CA copied: ${tokenCA}` }); } });这种方式在消息下方添加了按钮,点击后弹出包含CA的通知,但用户还是需要长按通知里的CA才能复制,没法实现消息内CA直接点击复制。
问题
如何让交易消息中的CA文本支持单次点击,点击后自动复制到剪贴板并显示Telegram的“已复制到剪贴板”系统通知?通过node-telegram-bot-api能否实现?如果Telegram Bot API不允许程序化访问剪贴板,有没有其他方式实现类似的用户体验?
额外信息
- node-telegram-bot-api版本:0.66.0
- Node.js版本:20.19.0
- 机器人运行环境:Raspberry Pi + systemd服务
内容的提问来源于stack exchange,提问作者Martin Wæhre

