โ† ะะฐะทะฐะด
// Telegram Price Alert Bot โ€” freelance template // Typical Upwork/Fiverr project: $200-500 // Stack: grammy + better-sqlite3 + Binance REST API const { Bot } = require('grammy') const Database = require('better-sqlite3') // ---- Config ---- const BOT_TOKEN = process.env.BOT_TOKEN || 'YOUR_BOT_TOKEN' const CHECK_INTERVAL = 15000 // check prices every 15s const BINANCE_API = 'https://fapi.binance.com' // ---- Database ---- const db = new Database('./alerts.db') db.pragma('journal_mode = WAL') db.exec(` CREATE TABLE IF NOT EXISTS alerts ( id INTEGER PRIMARY KEY AUTOINCREMENT, chat_id INTEGER NOT NULL, symbol TEXT NOT NULL, direction TEXT NOT NULL CHECK(direction IN ('above', 'below')), target REAL NOT NULL, created_at TEXT DEFAULT (datetime('now')), triggered INTEGER DEFAULT 0 ) `) const stmts = { insert: db.prepare('INSERT INTO alerts (chat_id, symbol, direction, target) VALUES (?, ?, ?, ?)'), getByChat: db.prepare('SELECT * FROM alerts WHERE chat_id = ? AND triggered = 0 ORDER BY id'), getActive: db.prepare('SELECT * FROM alerts WHERE triggered = 0'), trigger: db.prepare('UPDATE alerts SET triggered = 1 WHERE id = ?'), delete: db.prepare('DELETE FROM alerts WHERE id = ? AND chat_id = ?'), } // ---- Binance Price Fetcher ---- async function getPrice(symbol) { const sym = symbol.toUpperCase().replace('USDT', '') + 'USDT' const resp = await fetch(`${BINANCE_API}/fapi/v1/ticker/price?symbol=${sym}`) if (!resp.ok) return null const data = await resp.json() return { symbol: data.symbol, price: parseFloat(data.price) } } async function getPrices(symbols) { const resp = await fetch(`${BINANCE_API}/fapi/v1/ticker/price`) if (!resp.ok) return new Map() const data = await resp.json() const map = new Map() for (const t of data) { map.set(t.symbol, parseFloat(t.price)) } return map } // ---- Bot ---- const bot = new Bot(BOT_TOKEN) bot.command('start', (ctx) => { ctx.reply( '๐Ÿ”” *Price Alert Bot*\n\n' + 'Commands:\n' + '`/price BTC` โ€” current price\n' + '`/alert BTC above 70000` โ€” set alert\n' + '`/alert ETH below 3000` โ€” set alert\n' + '`/alerts` โ€” view active alerts\n' + '`/delete 3` โ€” remove alert by ID', { parse_mode: 'Markdown' } ) }) bot.command('price', async (ctx) => { const args = ctx.message.text.split(' ') if (args.length < 2) return ctx.reply('Usage: /price BTC') const coin = args[1].toUpperCase() const data = await getPrice(coin) if (!data) return ctx.reply(`โŒ Symbol ${coin} not found`) ctx.reply(`๐Ÿ’ฐ *${coin}*: $${data.price.toLocaleString('en-US')}`, { parse_mode: 'Markdown' }) }) bot.command('alert', (ctx) => { const args = ctx.message.text.split(' ') // /alert BTC above 70000 if (args.length < 4) return ctx.reply('Usage: /alert BTC above 70000') const coin = args[1].toUpperCase() const direction = args[2].toLowerCase() const target = parseFloat(args[3]) if (!['above', 'below'].includes(direction)) { return ctx.reply('โŒ Direction must be `above` or `below`') } if (isNaN(target) || target <= 0) { return ctx.reply('โŒ Invalid target price') } const symbol = coin.replace('USDT', '') + 'USDT' stmts.insert.run(ctx.chat.id, symbol, direction, target) const arrow = direction === 'above' ? '๐Ÿ“ˆ' : '๐Ÿ“‰' ctx.reply(`${arrow} Alert set: *${coin}* ${direction} $${target.toLocaleString('en-US')}`, { parse_mode: 'Markdown' }) }) bot.command('alerts', (ctx) => { const alerts = stmts.getByChat.all(ctx.chat.id) if (alerts.length === 0) return ctx.reply('No active alerts. Use /alert to create one.') const lines = alerts.map(a => { const coin = a.symbol.replace('USDT', '') const arrow = a.direction === 'above' ? '๐Ÿ“ˆ' : '๐Ÿ“‰' return `${arrow} #${a.id} *${coin}* ${a.direction} $${a.target.toLocaleString('en-US')}` }) ctx.reply('๐Ÿ”” *Active Alerts:*\n\n' + lines.join('\n'), { parse_mode: 'Markdown' }) }) bot.command('delete', (ctx) => { const args = ctx.message.text.split(' ') if (args.length < 2) return ctx.reply('Usage: /delete 3') const id = parseInt(args[1]) if (isNaN(id)) return ctx.reply('โŒ Invalid alert ID') const result = stmts.delete.run(id, ctx.chat.id) if (result.changes > 0) { ctx.reply(`โœ… Alert #${id} deleted`) } else { ctx.reply(`โŒ Alert #${id} not found`) } }) // ---- Price Checker Loop ---- async function checkAlerts() { const alerts = stmts.getActive.all() if (alerts.length === 0) return const prices = await getPrices() for (const alert of alerts) { const price = prices.get(alert.symbol) if (!price) continue const triggered = (alert.direction === 'above' && price >= alert.target) || (alert.direction === 'below' && price <= alert.target) if (triggered) { stmts.trigger.run(alert.id) const coin = alert.symbol.replace('USDT', '') const arrow = alert.direction === 'above' ? '๐Ÿš€' : '๐Ÿ”ป' try { await bot.api.sendMessage( alert.chat_id, `${arrow} *ALERT TRIGGERED!*\n\n` + `*${coin}* is now $${price.toLocaleString('en-US')}\n` + `Target: ${alert.direction} $${alert.target.toLocaleString('en-US')}`, { parse_mode: 'Markdown' } ) } catch (err) { console.error(`[Alert] Failed to notify chat ${alert.chat_id}:`, err.message) } } } } // ---- Start ---- async function main() { console.log('[PriceAlertBot] Starting...') // Price check loop setInterval(checkAlerts, CHECK_INTERVAL) // Start bot await bot.start({ onStart: () => console.log('[PriceAlertBot] Bot is running!'), }) } main().catch(console.error)