Most workout tracking apps are overengineered. You just want to quickly log βsquat 100kg 5x5β between sets without navigating through 12 screens. A Telegram bot is perfect for this β you already have it open, itβs instant, and you can log from your watch.
In this tutorial, weβll build a bot that understands natural language workout messages, stores your history in SQLite, and sends you a weekly AI-generated progress report.
What weβre building
You: bench press 80kg 3x8
Bot: β
Logged: Bench Press β 80kg Γ 8 Γ 8 Γ 8
You: squat 100kg 5x5
Bot: β
Logged: Squat β 100kg Γ 5 Γ 5 Γ 5 Γ 5 Γ 5
You: /stats
Bot: π This week:
β’ 4 sessions, 12 exercises
β’ Total volume: 14,200 kg
β’ PR: Squat 100kg Γ 5 (up from 95kg!)
You: /weekly
Bot: ποΈ Weekly Summary:
Your push volume increased 12% this week. Bench press
is progressing well β consider adding a pause rep set
next week. Your squat frequency dropped to 1x this
week, down from 2x. Try to hit legs twice next week.
Prerequisites
- Node.js 20+
- A Telegram account
- An Anthropic API key
- BotFather token (weβll create this)
Step 1: Create the Telegram bot
Open Telegram, search for @BotFather, and send:
/newbot
Follow the prompts β give it a name like βGym Trackerβ and a username like my_gym_tracker_bot. BotFather gives you a token like 7123456789:AAH.... Save it.
Step 2: Set up the project
mkdir gym-bot && cd gym-bot
npm init -y
npm install node-telegram-bot-api @anthropic-ai/sdk better-sqlite3
Step 3: Create the database
Create db.js:
import Database from 'better-sqlite3';
const db = new Database('workouts.sqlite');
db.exec(`
CREATE TABLE IF NOT EXISTS workouts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
exercise TEXT NOT NULL,
weight REAL,
unit TEXT DEFAULT 'kg',
reps TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
`);
export function logWorkout(userId, exercise, weight, unit, reps) {
db.prepare(
'INSERT INTO workouts (user_id, exercise, weight, unit, reps) VALUES (?, ?, ?, ?, ?)'
).run(userId, exercise, weight, unit, JSON.stringify(reps));
}
export function getWeekWorkouts(userId) {
return db.prepare(
`SELECT * FROM workouts WHERE user_id = ? AND created_at >= datetime('now', '-7 days') ORDER BY created_at`
).all(userId);
}
export function getAllWorkouts(userId) {
return db.prepare(
'SELECT * FROM workouts WHERE user_id = ? ORDER BY created_at DESC LIMIT 200'
).all(userId);
}
Step 4: Parse workout messages
Create parser.js:
// Parses messages like "bench press 80kg 3x8" or "deadlift 140 5x3"
export function parseWorkout(text) {
const cleaned = text.trim().toLowerCase();
// Match: exercise name, weight (optional), sets x reps
const match = cleaned.match(
/^(.+?)\s+(\d+(?:\.\d+)?)\s*(kg|lbs?)?\s*(\d+)\s*x\s*(\d+)$/
);
if (!match) return null;
const [, exercise, weight, unit, sets, reps] = match;
return {
exercise: exercise.trim().replace(/\b\w/g, c => c.toUpperCase()),
weight: parseFloat(weight),
unit: unit?.replace('lbs', 'lb') || 'kg',
reps: Array(parseInt(sets)).fill(parseInt(reps)),
};
}
Step 5: Build the bot
Create index.js:
import TelegramBot from 'node-telegram-bot-api';
import Anthropic from '@anthropic-ai/sdk';
import { logWorkout, getWeekWorkouts } from './db.js';
import { parseWorkout } from './parser.js';
const bot = new TelegramBot(process.env.TELEGRAM_TOKEN, { polling: true });
const anthropic = new Anthropic();
// Handle workout messages
bot.on('message', async (msg) => {
if (!msg.text || msg.text.startsWith('/')) return;
const workout = parseWorkout(msg.text);
if (!workout) {
bot.sendMessage(msg.chat.id,
'π€ Try: "bench press 80kg 3x8" or "squat 100 5x5"'
);
return;
}
logWorkout(msg.from.id, workout.exercise, workout.weight, workout.unit, workout.reps);
const repsStr = workout.reps.join(' Γ ');
bot.sendMessage(msg.chat.id,
`β
Logged: ${workout.exercise} β ${workout.weight}${workout.unit} Γ ${repsStr}`
);
});
// /stats command β quick weekly overview
bot.onText(/\/stats/, async (msg) => {
const workouts = getWeekWorkouts(msg.from.id);
if (workouts.length === 0) {
bot.sendMessage(msg.chat.id, 'π No workouts logged this week. Get after it!');
return;
}
const exercises = new Set(workouts.map(w => w.exercise));
const sessions = new Set(workouts.map(w => w.created_at.split('T')[0]));
let totalVolume = 0;
for (const w of workouts) {
const reps = JSON.parse(w.reps);
totalVolume += reps.reduce((sum, r) => sum + r, 0) * (w.weight || 0);
}
bot.sendMessage(msg.chat.id,
`π This week:\nβ’ ${sessions.size} session(s), ${workouts.length} exercises\nβ’ ${exercises.size} unique movements\nβ’ Total volume: ${totalVolume.toLocaleString()} ${workouts[0].unit}`
);
});
// /weekly command β AI-generated progress report
bot.onText(/\/weekly/, async (msg) => {
const workouts = getWeekWorkouts(msg.from.id);
if (workouts.length === 0) {
bot.sendMessage(msg.chat.id, 'No workouts this week to analyze.');
return;
}
const summary = workouts.map(w => {
const reps = JSON.parse(w.reps);
return `${w.created_at}: ${w.exercise} ${w.weight}${w.unit} ${reps.join('Γ')}`;
}).join('\n');
bot.sendMessage(msg.chat.id, 'π Generating your weekly report...');
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 500,
messages: [{
role: 'user',
content: `You're a knowledgeable gym coach. Analyze this week's workout log and give a brief progress report (4-5 sentences). Note any PRs, volume trends, and give one specific suggestion for next week. Be encouraging but honest. Keep it casual.
${summary}`
}],
});
bot.sendMessage(msg.chat.id, `ποΈ Weekly Report:\n\n${message.content[0].text}`);
});
// /help command
bot.onText(/\/help|\/start/, (msg) => {
bot.sendMessage(msg.chat.id,
`ποΈ *Gym Tracker Bot*\n\nLog workouts by sending messages like:\nβ’ \`bench press 80kg 3x8\`\nβ’ \`squat 100kg 5x5\`\nβ’ \`deadlift 140kg 1x3\`\n\nCommands:\n/stats β Quick weekly overview\n/weekly β AI-powered progress report\n/help β This message`,
{ parse_mode: 'Markdown' }
);
});
console.log('ποΈ Gym bot is running...');
Step 6: Run it
export TELEGRAM_TOKEN=your-botfather-token
export ANTHROPIC_API_KEY=your-anthropic-key
node index.js
Open your bot in Telegram and start logging workouts.
Deploying it
For 24/7 uptime, deploy to a cheap VPS or use a free tier:
# On a VPS with pm2
npm install -g pm2
pm2 start index.js --name gym-bot
pm2 save
pm2 startup
Or use a Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
CMD ["node", "index.js"]
The SQLite database file persists on disk, so mount a volume if using Docker.
Extending it
Ideas to make it better:
- Exercise aliases β map βbpβ to βBench Pressβ, βsqβ to βSquatβ
- Bodyweight tracking β
/weight 82.5to log daily weigh-ins - PR detection β compare against historical data and celebrate new records
- Scheduled summaries β use
node-cronto send weekly reports every Sunday evening - Charts β generate progress charts with
chart.jsand send as images
What you learned
- How to create a Telegram bot with BotFather and
node-telegram-bot-api - How to store structured data in SQLite from chat messages
- How to parse natural language input with regex
- How to generate AI-powered insights from user data
The whole bot is about 120 lines of code. Itβs the kind of project that starts as a weekend hack and becomes something you actually use every day.
Related resources
- npm complete guide β managing dependencies
- Docker complete guide β deploying with containers
- Linux complete guide β running on a VPS