πŸ“ Tutorials
Β· 5 min read

Build a Telegram Bot That Tracks Your Gym Workouts With AI


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.5 to log daily weigh-ins
  • PR detection β€” compare against historical data and celebrate new records
  • Scheduled summaries β€” use node-cron to send weekly reports every Sunday evening
  • Charts β€” generate progress charts with chart.js and 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.

πŸ“˜