โ ะะฐะทะฐะด// 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)